/*-------------------------------------------------------------------------
 *
 * acl.c
 *	  基本访问控制列表数据结构操作例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/acl.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>

#include "access/htup_details.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_class.h"
#include "catalog/pg_database.h"
#include "catalog/pg_parameter_acl.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/proclang.h"
#include "commands/tablespace.h"
#include "common/hashfn.h"
#include "foreign/foreign.h"
#include "funcapi.h"
#include "lib/qunique.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
#include "utils/varlena.h"

typedef struct
{
	const char *name;
	AclMode		value;
} priv_map;

/*
 * 我们经常需要测试给定角色是否是某个其他
 * 角色的成员。在大多数这些测试中，“给定角色”
 * 是相同的，即当前活跃用户。因此，我们可以通过
 * 保持缓存列表来优化它，其中列出了“给定角色”
 * 直接或间接是其成员的所有角色。
 *
 * 可能这个机制应该被泛化，以允许缓存多个角色的
 * 成员信息？
 *
 * cached_roles 的每个元素都是 OID 列表，对应于
 * cached_role 的相应元素（总是包括 cached_role
 * 本身）。一个缓存具有 ROLERECURSE_PRIVS 语义，另一个
 * 具有 ROLERECURSE_MEMBERS 语义。
 */
enum RoleRecurseType
{
	ROLERECURSE_PRIVS = 0,		/* 如果 rolinherit 则递归 */
	ROLERECURSE_MEMBERS = 1		/* 无条件递归 */
};
static Oid	cached_role[] = {InvalidOid, InvalidOid};
static List *cached_roles[] = {NIL, NIL};
static uint32 cached_db_hash;


static const char *fc_getid(const char *fc_s, char *fc_n);
static void fc_putid(char *fc_p, const char *fc_s);
static Acl *fc_allocacl(int fc_n);
static void fc_check_acl(const Acl *fc_acl);
static const char *fc_aclparse(const char *fc_s, AclItem *fc_aip);
static bool fc_aclitem_match(const AclItem *fc_a1, const AclItem *fc_a2);
static int	fc_aclitemComparator(const void *fc_arg1, const void *fc_arg2);
static void fc_check_circularity(const Acl *fc_old_acl, const AclItem *fc_mod_aip,
							  Oid fc_ownerId);
static Acl *fc_recursive_revoke(Acl *fc_acl, Oid fc_grantee, AclMode fc_revoke_privs,
							 Oid fc_ownerId, DropBehavior fc_behavior);

static AclMode fc_convert_priv_string(text *fc_priv_type_text);
static AclMode fc_convert_any_priv_string(text *fc_priv_type_text,
									   const priv_map *fc_privileges);

static Oid	fc_convert_table_name(text *fc_tablename);
static AclMode fc_convert_table_priv_string(text *fc_priv_type_text);
static AclMode fc_convert_sequence_priv_string(text *fc_priv_type_text);
static AttrNumber fc_convert_column_name(Oid fc_tableoid, text *fc_column);
static AclMode fc_convert_column_priv_string(text *fc_priv_type_text);
static Oid	fc_convert_database_name(text *fc_databasename);
static AclMode fc_convert_database_priv_string(text *fc_priv_type_text);
static Oid	fc_convert_foreign_data_wrapper_name(text *fc_fdwname);
static AclMode fc_convert_foreign_data_wrapper_priv_string(text *fc_priv_type_text);
static Oid	fc_convert_function_name(text *fc_functionname);
static AclMode fc_convert_function_priv_string(text *fc_priv_type_text);
static Oid	fc_convert_language_name(text *fc_languagename);
static AclMode fc_convert_language_priv_string(text *fc_priv_type_text);
static Oid	fc_convert_schema_name(text *fc_schemaname);
static AclMode fc_convert_schema_priv_string(text *fc_priv_type_text);
static Oid	fc_convert_server_name(text *fc_servername);
static AclMode fc_convert_server_priv_string(text *fc_priv_type_text);
static Oid	fc_convert_tablespace_name(text *fc_tablespacename);
static AclMode fc_convert_tablespace_priv_string(text *fc_priv_type_text);
static Oid	fc_convert_type_name(text *fc_typename);
static AclMode fc_convert_type_priv_string(text *fc_priv_type_text);
static AclMode fc_convert_parameter_priv_string(text *fc_priv_text);
static AclMode fc_convert_role_priv_string(text *fc_priv_type_text);
static AclResult pg_role_aclcheck(Oid fc_role_oid, Oid fc_roleid, AclMode fc_mode);

static void fc_RoleMembershipCacheCallback(Datum fc_arg, int fc_cacheid, uint32 fc_hashvalue);


/*
 * getid
 *		从字符串 's' 中消耗并解析第一个字母数字字符串（标识符），
 *		忽略任何前导空白。如果找到双引号，
 *		它返回引号内的单词。
 *
 * 返回：
 *		在 's' 中指向下一个非空字符的位置，
 *		经过任何引号之后。同时：
 *		- 将标识符加载到 'n' 中。（如果没有找到标识符，'n'
 *		  将包含空字符串。）'n' 必须是 NAMEDATALEN 字节。
 */
static const char * fc_getid(const char *fc_s, char *fc_n)
{
	int			fc_len = 0;
	bool		fc_in_quotes = false;

	Assert(fc_s && fc_n);

	while (isspace((unsigned char) *fc_s))
		fc_s++;
	/* 该代码最好与下面的 putid() 匹配 */
	for (;
		 *fc_s != '\0' &&
		 (isalnum((unsigned char) *fc_s) ||
		  *fc_s == '_' ||
		  *fc_s == '"' ||
		  fc_in_quotes);
		 fc_s++)
	{
		if (*fc_s == '"')
		{
			/* 可以安全查看下一个字符（不过可能是 '\0'） */
			if (*(fc_s + 1) != '"')
			{
				fc_in_quotes = !fc_in_quotes;
				continue;
			}
			/* 这是一个转义的双引号；跳过转义字符 */
			fc_s++;
		}

		/* 将字符添加到字符串中 */
		if (fc_len >= NAMEDATALEN - 1)
			ereport(ERROR,
					(errcode(ERRCODE_NAME_TOO_LONG),
					 errmsg("identifier too long"),
					 errdetail("Identifier must be less than %d characters.",
							   NAMEDATALEN)));

		fc_n[fc_len++] = *fc_s;
	}
	fc_n[fc_len] = '\0';
	while (isspace((unsigned char) *fc_s))
		fc_s++;
	return fc_s;
}

/*
 * 在 *p 处写入角色名称，如果需要则添加双引号。
 * *p 必须至少有 (2*NAMEDATALEN)+2 字节可用。
 * 这需要与 pg_dump/dumputils.c 中的 copyAclUserName 保持同步。
 */
static void fc_putid(char *fc_p, const char *fc_s)
{
	const char *fc_src;
	bool		fc_safe = true;

	for (fc_src = fc_s; *fc_src; fc_src++)
	{
		/* 该测试最好与上面的 getid() 匹配 */
		if (!isalnum((unsigned char) *fc_src) && *fc_src != '_')
		{
			fc_safe = false;
			break;
		}
	}
	if (!fc_safe)
		*fc_p++ = '"';
	for (fc_src = fc_s; *fc_src; fc_src++)
	{
		/* 用户名中的双引号字符编码为 "" */
		if (*fc_src == '"')
			*fc_p++ = '"';
		*fc_p++ = *fc_src;
	}
	if (!fc_safe)
		*fc_p++ = '"';
	*fc_p = '\0';
}

/*
 * aclparse
 *		消费并解析格式为：
 *				[group|user] [A-Za-z0-9]*=[rwaR]*
 *		的ACL规范，从字符串's'中，忽略任何前导空白或可选的id类型关键词（group|user）与实际
 *		ACL规范之间的空白。
 *
 *		在角色世界中，group|user的装饰是多余的，
 *		但为了向后兼容，我们仍然接受它。
 *
 *		这个例程由解析器和aclitemin()调用，因此
 *		增加了通用性。
 *
 * 返回：
 *		字符串在's'中立即跟随ACL
 *		规范的位置。此外：
 *		- 用适当的UID/GID、id类型标识符和模式类型值加载指向'aip'的结构。
 */
static const char * fc_aclparse(const char *fc_s, AclItem *fc_aip)
{
	AclMode		fc_privs,
				fc_goption,
				fc_read;
	char		fc_name[NAMEDATALEN];
	char		fc_name2[NAMEDATALEN];

	Assert(fc_s && fc_aip);

	fc_s = fc_getid(fc_s, fc_name);
	if (*fc_s != '=')
	{
		/* 我们刚刚读取了一个关键字，而不是一个名称 */
		if (strcmp(fc_name, "group") != 0 && strcmp(fc_name, "user") != 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
					 errmsg("unrecognized key word: \"%s\"", fc_name),
					 errhint("ACL key word must be \"group\" or \"user\".")));
		fc_s = fc_getid(fc_s, fc_name);		/* 将 s 移动到关键字之后的名称 */
		if (fc_name[0] == '\0')
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
					 errmsg("missing name"),
					 errhint("A name must follow the \"group\" or \"user\" key word.")));
	}

	if (*fc_s != '=')
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("missing \"=\" sign")));

	fc_privs = fc_goption = ACL_NO_RIGHTS;

	for (++fc_s, fc_read = 0; isalpha((unsigned char) *fc_s) || *fc_s == '*'; fc_s++)
	{
		switch (*fc_s)
		{
			case '*':
				fc_goption |= fc_read;
				break;
			case ACL_INSERT_CHR:
				fc_read = ACL_INSERT;
				break;
			case ACL_SELECT_CHR:
				fc_read = ACL_SELECT;
				break;
			case ACL_UPDATE_CHR:
				fc_read = ACL_UPDATE;
				break;
			case ACL_DELETE_CHR:
				fc_read = ACL_DELETE;
				break;
			case ACL_TRUNCATE_CHR:
				fc_read = ACL_TRUNCATE;
				break;
			case ACL_REFERENCES_CHR:
				fc_read = ACL_REFERENCES;
				break;
			case ACL_TRIGGER_CHR:
				fc_read = ACL_TRIGGER;
				break;
			case ACL_EXECUTE_CHR:
				fc_read = ACL_EXECUTE;
				break;
			case ACL_USAGE_CHR:
				fc_read = ACL_USAGE;
				break;
			case ACL_CREATE_CHR:
				fc_read = ACL_CREATE;
				break;
			case ACL_CREATE_TEMP_CHR:
				fc_read = ACL_CREATE_TEMP;
				break;
			case ACL_CONNECT_CHR:
				fc_read = ACL_CONNECT;
				break;
			case ACL_SET_CHR:
				fc_read = ACL_SET;
				break;
			case ACL_ALTER_SYSTEM_CHR:
				fc_read = ACL_ALTER_SYSTEM;
				break;
			case 'R':			/* 忽略旧的 RULE 特权 */
				fc_read = 0;
				break;
			default:
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
						 errmsg("invalid mode character: must be one of \"%s\"",
								ACL_ALL_RIGHTS_STR)));
		}

		fc_privs |= fc_read;
	}

	if (fc_name[0] == '\0')
		fc_aip->ai_grantee = ACL_ID_PUBLIC;
	else
		fc_aip->ai_grantee = get_role_oid(fc_name, false);

	/*
	 * XXX 默认将授予者设为超级用户，以允许一定程度的向后兼容性。
	 */
	if (*fc_s == '/')
	{
		fc_s = fc_getid(fc_s + 1, fc_name2);
		if (fc_name2[0] == '\0')
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
					 errmsg("a name must follow the \"/\" sign")));
		fc_aip->ai_grantor = get_role_oid(fc_name2, false);
	}
	else
	{
		fc_aip->ai_grantor = BOOTSTRAP_SUPERUSERID;
		ereport(WARNING,
				(errcode(ERRCODE_INVALID_GRANTOR),
				 errmsg("defaulting grantor to user ID %u",
						BOOTSTRAP_SUPERUSERID)));
	}

	ACLITEM_SET_PRIVS_GOPTIONS(*fc_aip, fc_privs, fc_goption);

	return fc_s;
}

/*
 * allocacl
 *		为具有 'n' 条目的新 Acl 分配存储。
 *
 * 返回：
 *		新的 Acl
 */
static Acl * fc_allocacl(int fc_n)
{
	Acl		   *fc_new_acl;
	Size		fc_size;

	if (fc_n < 0)
		elog(ERROR, "invalid size: %d", fc_n);
	fc_size = ACL_N_SIZE(fc_n);
	fc_new_acl = (Acl *) palloc0(fc_size);
	SET_VARSIZE(fc_new_acl, fc_size);
	fc_new_acl->ndim = 1;
	fc_new_acl->dataoffset = 0;	/* 我们从未插入任何空值 */
	fc_new_acl->elemtype = ACLITEMOID;
	ARR_LBOUND(fc_new_acl)[0] = 1;
	ARR_DIMS(fc_new_acl)[0] = fc_n;
	return fc_new_acl;
}

/*
 * 创建一个零条目的 ACL
 */
Acl * make_empty_acl(void)
{
	return fc_allocacl(0);
}

/*
 * 复制一个 ACL
 */
Acl * aclcopy(const Acl *fc_orig_acl)
{
	Acl		   *fc_result_acl;

	fc_result_acl = fc_allocacl(ACL_NUM(fc_orig_acl));

	memcpy(ACL_DAT(fc_result_acl),
		   ACL_DAT(fc_orig_acl),
		   ACL_NUM(fc_orig_acl) * sizeof(AclItem));

	return fc_result_acl;
}

/*
 * 连接两个 ACL
 *
 * 这有点简单，因为我们可能会生成一个具有冗余条目的 ACL。
 * 小心结果的用途！
 */
Acl * aclconcat(const Acl *fc_left_acl, const Acl *fc_right_acl)
{
	Acl		   *fc_result_acl;

	fc_result_acl = fc_allocacl(ACL_NUM(fc_left_acl) + ACL_NUM(fc_right_acl));

	memcpy(ACL_DAT(fc_result_acl),
		   ACL_DAT(fc_left_acl),
		   ACL_NUM(fc_left_acl) * sizeof(AclItem));

	memcpy(ACL_DAT(fc_result_acl) + ACL_NUM(fc_left_acl),
		   ACL_DAT(fc_right_acl),
		   ACL_NUM(fc_right_acl) * sizeof(AclItem));

	return fc_result_acl;
}

/*
 * 合并两个 ACL
 *
 * 这会产生一个没有冗余条目的正确合并 ACL。
 * 在输入为 NULL 时返回 NULL。
 */
Acl * aclmerge(const Acl *fc_left_acl, const Acl *fc_right_acl, Oid fc_ownerId)
{
	Acl		   *fc_result_acl;
	AclItem    *fc_aip;
	int			fc_i,
				fc_num;

	/* 检查一个或两个为空/NULL 的情况 */
	if (fc_left_acl == NULL || ACL_NUM(fc_left_acl) == 0)
	{
		if (fc_right_acl == NULL || ACL_NUM(fc_right_acl) == 0)
			return NULL;
		else
			return aclcopy(fc_right_acl);
	}
	else
	{
		if (fc_right_acl == NULL || ACL_NUM(fc_right_acl) == 0)
			return aclcopy(fc_left_acl);
	}

	/* 逐个项目逐步合并它们 */
	fc_result_acl = aclcopy(fc_left_acl);

	fc_aip = ACL_DAT(fc_right_acl);
	fc_num = ACL_NUM(fc_right_acl);

	for (fc_i = 0; fc_i < fc_num; fc_i++, fc_aip++)
	{
		Acl		   *fc_tmp_acl;

		fc_tmp_acl = aclupdate(fc_result_acl, fc_aip, ACL_MODECHG_ADD,
							fc_ownerId, DROP_RESTRICT);
		pfree(fc_result_acl);
		fc_result_acl = fc_tmp_acl;
	}

	return fc_result_acl;
}

/*
 * 对 ACL 中的项目进行排序（按任意但一致的顺序）
 */
void aclitemsort(Acl *fc_acl)
{
	if (fc_acl != NULL && ACL_NUM(fc_acl) > 1)
		qsort(ACL_DAT(fc_acl), ACL_NUM(fc_acl), sizeof(AclItem), fc_aclitemComparator);
}

/*
 * 检查两个 ACL 是否完全相等
 *
 * 如果两个数组包含相同的项目但顺序不同，则无法检测相等性。
 * 为了处理这种情况，首先对两个输入进行排序，
 * 使用 aclitemsort()。
 */
bool aclequal(const Acl *fc_left_acl, const Acl *fc_right_acl)
{
	/* 检查一个或两个为空/NULL 的情况 */
	if (fc_left_acl == NULL || ACL_NUM(fc_left_acl) == 0)
	{
		if (fc_right_acl == NULL || ACL_NUM(fc_right_acl) == 0)
			return true;
		else
			return false;
	}
	else
	{
		if (fc_right_acl == NULL || ACL_NUM(fc_right_acl) == 0)
			return false;
	}

	if (ACL_NUM(fc_left_acl) != ACL_NUM(fc_right_acl))
		return false;

	if (memcmp(ACL_DAT(fc_left_acl),
			   ACL_DAT(fc_right_acl),
			   ACL_NUM(fc_left_acl) * sizeof(AclItem)) == 0)
		return true;

	return false;
}

/*
 * 验证 ACL 数组是否可接受（一维且没有空值）
 */
static void fc_check_acl(const Acl *fc_acl)
{
	if (ARR_ELEMTYPE(fc_acl) != ACLITEMOID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("ACL array contains wrong data type")));
	if (ARR_NDIM(fc_acl) != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("ACL arrays must be one-dimensional")));
	if (ARR_HASNULL(fc_acl))
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("ACL arrays must not contain null values")));
}

/*
 * aclitemin
 *		为给定字符串 's' 分配存储并填充新 AclItem，该字符串
 *		包含一个 ACL 规范。有关详情，请参见 aclparse。
 *
 * 返回：
 *		新的 AclItem
 */
Datum aclitemin(PG_FUNCTION_ARGS)
{
	const char *fc_s = PG_GETARG_CSTRING(0);
	AclItem    *fc_aip;

	fc_aip = (AclItem *) palloc(sizeof(AclItem));
	fc_s = fc_aclparse(fc_s, fc_aip);
	while (isspace((unsigned char) *fc_s))
		++fc_s;
	if (*fc_s)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("extra garbage at the end of the ACL specification")));

	PG_RETURN_ACLITEM_P(fc_aip);
}

/*
 * aclitemout
 *		为包含格式化 ACL 规范的新空分隔字符串分配存储并填充。
 *		有关详情，请参见 aclparse。
 *
 * 返回：
 *		新的字符串
 */
Datum aclitemout(PG_FUNCTION_ARGS)
{
	AclItem    *fc_aip = PG_GETARG_ACLITEM_P(0);
	char	   *fc_p;
	char	   *fc_out;
	HeapTuple	fc_htup;
	unsigned	fc_i;

	fc_out = palloc(strlen("=/") +
				 2 * N_ACL_RIGHTS +
				 2 * (2 * NAMEDATALEN + 2) +
				 1);

	fc_p = fc_out;
	*fc_p = '\0';

	if (fc_aip->ai_grantee != ACL_ID_PUBLIC)
	{
		fc_htup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(fc_aip->ai_grantee));
		if (HeapTupleIsValid(fc_htup))
		{
			fc_putid(fc_p, NameStr(((Form_pg_authid) GETSTRUCT(fc_htup))->rolname));
			ReleaseSysCache(fc_htup);
		}
		else
		{
			/* 如果没有找到条目则生成数字 OID */
			sprintf(fc_p, "%u", fc_aip->ai_grantee);
		}
	}
	while (*fc_p)
		++fc_p;

	*fc_p++ = '=';

	for (fc_i = 0; fc_i < N_ACL_RIGHTS; ++fc_i)
	{
		if (ACLITEM_GET_PRIVS(*fc_aip) & (1 << fc_i))
			*fc_p++ = ACL_ALL_RIGHTS_STR[fc_i];
		if (ACLITEM_GET_GOPTIONS(*fc_aip) & (1 << fc_i))
			*fc_p++ = '*';
	}

	*fc_p++ = '/';
	*fc_p = '\0';

	fc_htup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(fc_aip->ai_grantor));
	if (HeapTupleIsValid(fc_htup))
	{
		fc_putid(fc_p, NameStr(((Form_pg_authid) GETSTRUCT(fc_htup))->rolname));
		ReleaseSysCache(fc_htup);
	}
	else
	{
		/* 如果没有找到条目则生成数字 OID */
		sprintf(fc_p, "%u", fc_aip->ai_grantor);
	}

	PG_RETURN_CSTRING(fc_out);
}

/*
 * aclitem_match
 *		当且仅当两个 AclItems 具有相同的受让人和授予人时，才被视为匹配；
 *		权限被忽略。
 */
static bool fc_aclitem_match(const AclItem *fc_a1, const AclItem *fc_a2)
{
	return fc_a1->ai_grantee == fc_a2->ai_grantee &&
		fc_a1->ai_grantor == fc_a2->ai_grantor;
}

/*
 * aclitemComparator
 *		qsort 的 AclItems 比较函数
 */
static int fc_aclitemComparator(const void *fc_arg1, const void *fc_arg2)
{
	const AclItem *fc_a1 = (const AclItem *) fc_arg1;
	const AclItem *fc_a2 = (const AclItem *) fc_arg2;

	if (fc_a1->ai_grantee > fc_a2->ai_grantee)
		return 1;
	if (fc_a1->ai_grantee < fc_a2->ai_grantee)
		return -1;
	if (fc_a1->ai_grantor > fc_a2->ai_grantor)
		return 1;
	if (fc_a1->ai_grantor < fc_a2->ai_grantor)
		return -1;
	if (fc_a1->ai_privs > fc_a2->ai_privs)
		return 1;
	if (fc_a1->ai_privs < fc_a2->ai_privs)
		return -1;
	return 0;
}

/*
 * aclitem 相等运算符
 */
Datum aclitem_eq(PG_FUNCTION_ARGS)
{
	AclItem    *fc_a1 = PG_GETARG_ACLITEM_P(0);
	AclItem    *fc_a2 = PG_GETARG_ACLITEM_P(1);
	bool		fc_result;

	fc_result = fc_a1->ai_privs == fc_a2->ai_privs &&
		fc_a1->ai_grantee == fc_a2->ai_grantee &&
		fc_a1->ai_grantor == fc_a2->ai_grantor;
	PG_RETURN_BOOL(fc_result);
}

/*
 * aclitem 哈希函数
 *
 * 我们使 aclitems 可哈希，并非主要是因为有人可能会对其进行哈希，
 * 而是因为我们希望数组相等性在 aclitem 数组上工作，
 * 并且在 typcache 机制下我们必须有哈希或 btree 操作类。
 */
Datum hash_aclitem(PG_FUNCTION_ARGS)
{
	AclItem    *fc_a = PG_GETARG_ACLITEM_P(0);

	/* 不是非常聪明，但可以避免结构中的任何填充问题 */
	PG_RETURN_UINT32((uint32) (fc_a->ai_privs + fc_a->ai_grantee + fc_a->ai_grantor));
}

/*
 * 64 位 aclitem 哈希函数。
 *
 * 类似于 hash_aclitem，但接受种子并返回一个 uint64 值。
 */
Datum hash_aclitem_extended(PG_FUNCTION_ARGS)
{
	AclItem    *fc_a = PG_GETARG_ACLITEM_P(0);
	uint64		fc_seed = PG_GETARG_INT64(1);
	uint32		fc_sum = (uint32) (fc_a->ai_privs + fc_a->ai_grantee + fc_a->ai_grantor);

	return (fc_seed == 0) ? UInt64GetDatum(fc_sum) : hash_uint32_extended(fc_sum, fc_seed);
}

/*
 * acldefault()  --- 创建描述默认访问权限的 ACL
 *
 * 如果您想更改新创建对象（或任何具有 NULL acl 条目的对象）的默认访问策略，
 * 请更改此例程。更改时，请不要忘记更新 GRANT 手册页，
 * 该页解释了所有默认权限。
 *
 * 请注意，这些是硬编码的“默认值”，在没有任何 pg_default_acl 条目的情况下使用。
 */
Acl * acldefault(ObjectType fc_objtype, Oid fc_ownerId)
{
	AclMode		fc_world_default;
	AclMode		fc_owner_default;
	int			fc_nacl;
	Acl		   *fc_acl;
	AclItem    *fc_aip;

	switch (fc_objtype)
	{
		case OBJECT_COLUMN:
			/* 默认情况下，列没有额外的权限 */
			fc_world_default = ACL_NO_RIGHTS;
			fc_owner_default = ACL_NO_RIGHTS;
			break;
		case OBJECT_TABLE:
			fc_world_default = ACL_NO_RIGHTS;
			fc_owner_default = ACL_ALL_RIGHTS_RELATION;
			break;
		case OBJECT_SEQUENCE:
			fc_world_default = ACL_NO_RIGHTS;
			fc_owner_default = ACL_ALL_RIGHTS_SEQUENCE;
			break;
		case OBJECT_DATABASE:
			/* 为了向后兼容，默认授予一些权限 */
			fc_world_default = ACL_CREATE_TEMP | ACL_CONNECT;
			fc_owner_default = ACL_ALL_RIGHTS_DATABASE;
			break;
		case OBJECT_FUNCTION:
			/* 暂时默认授予 EXECUTE */
			fc_world_default = ACL_EXECUTE;
			fc_owner_default = ACL_ALL_RIGHTS_FUNCTION;
			break;
		case OBJECT_LANGUAGE:
			/* 暂时默认授予 USAGE */
			fc_world_default = ACL_USAGE;
			fc_owner_default = ACL_ALL_RIGHTS_LANGUAGE;
			break;
		case OBJECT_LARGEOBJECT:
			fc_world_default = ACL_NO_RIGHTS;
			fc_owner_default = ACL_ALL_RIGHTS_LARGEOBJECT;
			break;
		case OBJECT_SCHEMA:
			fc_world_default = ACL_NO_RIGHTS;
			fc_owner_default = ACL_ALL_RIGHTS_SCHEMA;
			break;
		case OBJECT_TABLESPACE:
			fc_world_default = ACL_NO_RIGHTS;
			fc_owner_default = ACL_ALL_RIGHTS_TABLESPACE;
			break;
		case OBJECT_FDW:
			fc_world_default = ACL_NO_RIGHTS;
			fc_owner_default = ACL_ALL_RIGHTS_FDW;
			break;
		case OBJECT_FOREIGN_SERVER:
			fc_world_default = ACL_NO_RIGHTS;
			fc_owner_default = ACL_ALL_RIGHTS_FOREIGN_SERVER;
			break;
		case OBJECT_DOMAIN:
		case OBJECT_TYPE:
			fc_world_default = ACL_USAGE;
			fc_owner_default = ACL_ALL_RIGHTS_TYPE;
			break;
		case OBJECT_PARAMETER_ACL:
			fc_world_default = ACL_NO_RIGHTS;
			fc_owner_default = ACL_ALL_RIGHTS_PARAMETER_ACL;
			break;
		default:
			elog(ERROR, "unrecognized objtype: %d", (int) fc_objtype);
			fc_world_default = ACL_NO_RIGHTS;	/* 保持编译器安静 */
			fc_owner_default = ACL_NO_RIGHTS;
			break;
	}

	fc_nacl = 0;
	if (fc_world_default != ACL_NO_RIGHTS)
		fc_nacl++;
	if (fc_owner_default != ACL_NO_RIGHTS)
		fc_nacl++;

	fc_acl = fc_allocacl(fc_nacl);
	fc_aip = ACL_DAT(fc_acl);

	if (fc_world_default != ACL_NO_RIGHTS)
	{
		fc_aip->ai_grantee = ACL_ID_PUBLIC;
		fc_aip->ai_grantor = fc_ownerId;
		ACLITEM_SET_PRIVS_GOPTIONS(*fc_aip, fc_world_default, ACL_NO_RIGHTS);
		fc_aip++;
	}

	/*
	 * 请注意，所有者的条目显示所有普通权限，但没有授予选项。
	 * 这是因为他的授予选项来自“系统”而不是自己的努力。
	 * （SQL 规范规定，所有者的权利来自一个 "_SYSTEM" authid。）但是，
	 * 我们确实认为所有者的普通权限是自我授予的；这使他能够撤销它们。
	 * 我们在实现所有者的授予选项时没有任何明确的 "_SYSTEM"-like ACL 条目，
	 * 而是在我们测试授予选项的地方内部特殊处理所有者。
	 */
	if (fc_owner_default != ACL_NO_RIGHTS)
	{
		fc_aip->ai_grantee = fc_ownerId;
		fc_aip->ai_grantor = fc_ownerId;
		ACLITEM_SET_PRIVS_GOPTIONS(*fc_aip, fc_owner_default, ACL_NO_RIGHTS);
	}

	return fc_acl;
}


/*
 * SQL可访问版本的 acldefault()。将 "char" 类型黑客式映射到
 * OBJECT_* 值。
 */
Datum acldefault_sql(PG_FUNCTION_ARGS)
{
	char		fc_objtypec = PG_GETARG_CHAR(0);
	Oid			fc_owner = PG_GETARG_OID(1);
	ObjectType	fc_objtype = 0;

	switch (fc_objtypec)
	{
		case 'c':
			fc_objtype = OBJECT_COLUMN;
			break;
		case 'r':
			fc_objtype = OBJECT_TABLE;
			break;
		case 's':
			fc_objtype = OBJECT_SEQUENCE;
			break;
		case 'd':
			fc_objtype = OBJECT_DATABASE;
			break;
		case 'f':
			fc_objtype = OBJECT_FUNCTION;
			break;
		case 'l':
			fc_objtype = OBJECT_LANGUAGE;
			break;
		case 'L':
			fc_objtype = OBJECT_LARGEOBJECT;
			break;
		case 'n':
			fc_objtype = OBJECT_SCHEMA;
			break;
		case 'p':
			fc_objtype = OBJECT_PARAMETER_ACL;
			break;
		case 't':
			fc_objtype = OBJECT_TABLESPACE;
			break;
		case 'F':
			fc_objtype = OBJECT_FDW;
			break;
		case 'S':
			fc_objtype = OBJECT_FOREIGN_SERVER;
			break;
		case 'T':
			fc_objtype = OBJECT_TYPE;
			break;
		default:
			elog(ERROR, "unrecognized objtype abbreviation: %c", fc_objtypec);
	}

	PG_RETURN_ACL_P(acldefault(fc_objtype, fc_owner));
}


/*
 * 更新 ACL 数组以添加或移除指定的权限。
 *
 *	old_acl: 输入的 ACL 数组
 *	mod_aip: 定义要添加、移除或替换的权限
 *	modechg: ACL_MODECHG_ADD、ACL_MODECHG_DEL 或 ACL_MODECHG_EQL
 *	ownerId: 对象所有者的 Oid
 *	behavior: 用于递归删除的 RESTRICT 或 CASCADE 行为
 *
 * ownerid 和 behavior 仅在更新操作指定删除授予选项时相关。
 *
 * 结果是一个修改过的副本；输入对象没有改变。
 *
 * 注意：调用者负责在需要时对输入 ACL 进行解压。
 */
Acl * aclupdate(const Acl *fc_old_acl, const AclItem *fc_mod_aip,
		  int fc_modechg, Oid fc_ownerId, DropBehavior fc_behavior)
{
	Acl		   *fc_new_acl = NULL;
	AclItem    *fc_old_aip,
			   *fc_new_aip = NULL;
	AclMode		fc_old_rights,
				fc_old_goptions,
				fc_new_rights,
				fc_new_goptions;
	int			fc_dst,
				fc_num;

	/* 调用者可能已经检查了 old_acl，但要安全 */
	fc_check_acl(fc_old_acl);

	/* 如果授予授予选项，请检查循环性 */
	if (fc_modechg != ACL_MODECHG_DEL &&
		ACLITEM_GET_GOPTIONS(*fc_mod_aip) != ACL_NO_RIGHTS)
		fc_check_circularity(fc_old_acl, fc_mod_aip, fc_ownerId);

	fc_num = ACL_NUM(fc_old_acl);
	fc_old_aip = ACL_DAT(fc_old_acl);

	/*
	 * 在 ACL 中搜索此被授予者和授予者的现有条目。如果
	 * 存在，则在原位修改条目（实际上是在同一位置，
	 * 因为我们实际上返回的是一个副本）；否则，将新条目插入到
	 * 末尾。
	 */

	for (fc_dst = 0; fc_dst < fc_num; ++fc_dst)
	{
		if (fc_aclitem_match(fc_mod_aip, fc_old_aip + fc_dst))
		{
			/* 找到匹配项，因此修改现有项 */
			fc_new_acl = fc_allocacl(fc_num);
			fc_new_aip = ACL_DAT(fc_new_acl);
			memcpy(fc_new_acl, fc_old_acl, ACL_SIZE(fc_old_acl));
			break;
		}
	}

	if (fc_dst == fc_num)
	{
		/* 需要附加新项 */
		fc_new_acl = fc_allocacl(fc_num + 1);
		fc_new_aip = ACL_DAT(fc_new_acl);
		memcpy(fc_new_aip, fc_old_aip, fc_num * sizeof(AclItem));

		/* 用无权限初始化新条目 */
		fc_new_aip[fc_dst].ai_grantee = fc_mod_aip->ai_grantee;
		fc_new_aip[fc_dst].ai_grantor = fc_mod_aip->ai_grantor;
		ACLITEM_SET_PRIVS_GOPTIONS(fc_new_aip[fc_dst],
								   ACL_NO_RIGHTS, ACL_NO_RIGHTS);
		fc_num++;					/* 将 num 设置为 new_acl 的大小 */
	}

	fc_old_rights = ACLITEM_GET_RIGHTS(fc_new_aip[fc_dst]);
	fc_old_goptions = ACLITEM_GET_GOPTIONS(fc_new_aip[fc_dst]);

	/* 应用指定的权限更改 */
	switch (fc_modechg)
	{
		case ACL_MODECHG_ADD:
			ACLITEM_SET_RIGHTS(fc_new_aip[fc_dst],
							   fc_old_rights | ACLITEM_GET_RIGHTS(*fc_mod_aip));
			break;
		case ACL_MODECHG_DEL:
			ACLITEM_SET_RIGHTS(fc_new_aip[fc_dst],
							   fc_old_rights & ~ACLITEM_GET_RIGHTS(*fc_mod_aip));
			break;
		case ACL_MODECHG_EQL:
			ACLITEM_SET_RIGHTS(fc_new_aip[fc_dst],
							   ACLITEM_GET_RIGHTS(*fc_mod_aip));
			break;
	}

	fc_new_rights = ACLITEM_GET_RIGHTS(fc_new_aip[fc_dst]);
	fc_new_goptions = ACLITEM_GET_GOPTIONS(fc_new_aip[fc_dst]);

	/*
	 * 如果调整后的条目没有权限，则将其从列表中删除。
	 */
	if (fc_new_rights == ACL_NO_RIGHTS)
	{
		memmove(fc_new_aip + fc_dst,
				fc_new_aip + fc_dst + 1,
				(fc_num - fc_dst - 1) * sizeof(AclItem));
		/* 将数组大小调整为 'num - 1' 项 */
		ARR_DIMS(fc_new_acl)[0] = fc_num - 1;
		SET_VARSIZE(fc_new_acl, ACL_N_SIZE(fc_num - 1));
	}

	/*
	 * 移除无主的权限（级联撤销）。目前我们只能
	 * 在被授予者不是 PUBLIC 时处理此情况。
	 */
	if ((fc_old_goptions & ~fc_new_goptions) != 0)
	{
		Assert(fc_mod_aip->ai_grantee != ACL_ID_PUBLIC);
		fc_new_acl = fc_recursive_revoke(fc_new_acl, fc_mod_aip->ai_grantee,
								   (fc_old_goptions & ~fc_new_goptions),
								   fc_ownerId, fc_behavior);
	}

	return fc_new_acl;
}

/*
 * 更新 ACL 数组以反映所有权变更至父对象
 *
 *	old_acl: 输入的 ACL 数组（不能为空）
 *	oldOwnerId: 旧对象所有者的 Oid
 *	newOwnerId: 新对象所有者的 Oid
 *
 * 结果是一个修改过的副本；输入对象没有改变。
 *
 * 注意：调用者负责在需要时对输入 ACL 进行解压。
 */
Acl * aclnewowner(const Acl *fc_old_acl, Oid fc_oldOwnerId, Oid fc_newOwnerId)
{
	Acl		   *fc_new_acl;
	AclItem    *fc_new_aip;
	AclItem    *fc_old_aip;
	AclItem    *fc_dst_aip;
	AclItem    *fc_src_aip;
	AclItem    *fc_targ_aip;
	bool		fc_newpresent = false;
	int			fc_dst,
				fc_src,
				fc_targ,
				fc_num;

	fc_check_acl(fc_old_acl);

	/*
	 * 制作给定 ACL 的副本，将新所有者 ID 替换旧所有者 ID
	 * 无论其作为授予者还是被授予者出现。还要注意新
	 * 所有者 ID 是否已经存在。
	 */
	fc_num = ACL_NUM(fc_old_acl);
	fc_old_aip = ACL_DAT(fc_old_acl);
	fc_new_acl = fc_allocacl(fc_num);
	fc_new_aip = ACL_DAT(fc_new_acl);
	memcpy(fc_new_aip, fc_old_aip, fc_num * sizeof(AclItem));
	for (fc_dst = 0, fc_dst_aip = fc_new_aip; fc_dst < fc_num; fc_dst++, fc_dst_aip++)
	{
		if (fc_dst_aip->ai_grantor == fc_oldOwnerId)
			fc_dst_aip->ai_grantor = fc_newOwnerId;
		else if (fc_dst_aip->ai_grantor == fc_newOwnerId)
			fc_newpresent = true;
		if (fc_dst_aip->ai_grantee == fc_oldOwnerId)
			fc_dst_aip->ai_grantee = fc_newOwnerId;
		else if (fc_dst_aip->ai_grantee == fc_newOwnerId)
			fc_newpresent = true;
	}

	/*
	 * 如果旧 ACL 包含对新所有者的任何引用，那么我们现在
	 * 可能生成了包含重复条目的 ACL。查找它们并
	 * 合并以便没有重复。（这相对昂贵，因为我们使用的是
	 * 愚蠢的 O(N^2) 算法，但这不太可能是正常情况。）
	 *
	 * 为了简化重复条目的删除，我们暂时将它们留在
	 * 数组中，但将其权限掩码设置为零；当我们达到这样的
	 * 条目时，它将被跳过。（因此，该代码的一个副作用将是
	 * 移除没有权限的条目，如果输入中存在的话。）dst
	 * 是下一个输出槽，targ 是当前考虑的输入槽
	 * （始终 >= dst），src 扫描 targ 右侧的条目以查找
	 * 重复项。一旦将一个条目发出到 dst，它就被认为
	 * 是无重复的，不再考虑。
	 */
	if (fc_newpresent)
	{
		fc_dst = 0;
		for (fc_targ = 0, fc_targ_aip = fc_new_aip; fc_targ < fc_num; fc_targ++, fc_targ_aip++)
		{
			/* 如果在早期传递中被删除则忽略 */
			if (ACLITEM_GET_RIGHTS(*fc_targ_aip) == ACL_NO_RIGHTS)
				continue;
			/* 查找并合并任何重复项 */
			for (fc_src = fc_targ + 1, fc_src_aip = fc_targ_aip + 1; fc_src < fc_num;
				 fc_src++, fc_src_aip++)
			{
				if (ACLITEM_GET_RIGHTS(*fc_src_aip) == ACL_NO_RIGHTS)
					continue;
				if (fc_aclitem_match(fc_targ_aip, fc_src_aip))
				{
					ACLITEM_SET_RIGHTS(*fc_targ_aip,
									   ACLITEM_GET_RIGHTS(*fc_targ_aip) |
									   ACLITEM_GET_RIGHTS(*fc_src_aip));
					/* 标记重复项为已删除 */
					ACLITEM_SET_RIGHTS(*fc_src_aip, ACL_NO_RIGHTS);
				}
			}
			/* 并发出到输出 */
			fc_new_aip[fc_dst] = *fc_targ_aip;
			fc_dst++;
		}
		/* 将数组大小调整为 'dst' 项 */
		ARR_DIMS(fc_new_acl)[0] = fc_dst;
		SET_VARSIZE(fc_new_acl, ACL_N_SIZE(fc_dst));
	}

	return fc_new_acl;
}


/*
 * 在授予授予选项时，我们必须禁止设置循环
 * 授权选项链的尝试。假设 A（对象所有者）授予 B 一些
 * 权限并附带授予选项，而 B 再次授予它们给 C。
 * 如果 C 也能将这些权限授予 B，那么 A 将无法有效地
 * 撤销 B 的权限，因为 recursive_revoke 将认为
 * B 仍然拥有来自 C 的权限。
 *
 * 我们通过递归删除所有属于目标被授予者的授予选项来检查这一点，
 * 然后查看预计的授予者是否仍然拥有
 * 授予选项。
 */
static void fc_check_circularity(const Acl *fc_old_acl, const AclItem *fc_mod_aip,
				  Oid fc_ownerId)
{
	Acl		   *fc_acl;
	AclItem    *fc_aip;
	int			fc_i,
				fc_num;
	AclMode		fc_own_privs;

	fc_check_acl(fc_old_acl);

	/*
	 * 目前，授予选项只能授予角色，而不能授予 PUBLIC。
	 * 否则我们将不得不在这里更费劲。
	 */
	Assert(fc_mod_aip->ai_grantee != ACL_ID_PUBLIC);

	/* 所有者总是拥有授予选项，无需检查 */
	if (fc_mod_aip->ai_grantor == fc_ownerId)
		return;

	/* 制作工作副本 */
	fc_acl = fc_allocacl(ACL_NUM(fc_old_acl));
	memcpy(fc_acl, fc_old_acl, ACL_SIZE(fc_old_acl));

	/* 删除目标被授予者的所有授予选项，以及基于它们的选项 */
cc_restart:
	fc_num = ACL_NUM(fc_acl);
	fc_aip = ACL_DAT(fc_acl);
	for (fc_i = 0; fc_i < fc_num; fc_i++)
	{
		if (fc_aip[fc_i].ai_grantee == fc_mod_aip->ai_grantee &&
			ACLITEM_GET_GOPTIONS(fc_aip[fc_i]) != ACL_NO_RIGHTS)
		{
			Acl		   *fc_new_acl;

			/* 我们实际上也会删除普通权限，但无所谓 */
			fc_new_acl = aclupdate(fc_acl, &fc_aip[fc_i], ACL_MODECHG_DEL,
								fc_ownerId, DROP_CASCADE);

			pfree(fc_acl);
			fc_acl = fc_new_acl;

			goto cc_restart;
		}
	}

	/* 现在我们可以独立计算授予者的派生特权 */
	fc_own_privs = aclmask(fc_acl,
						fc_mod_aip->ai_grantor,
						fc_ownerId,
						ACL_GRANT_OPTION_FOR(ACLITEM_GET_GOPTIONS(*fc_mod_aip)),
						ACLMASK_ALL);
	fc_own_privs = ACL_OPTION_TO_PRIVS(fc_own_privs);

	if ((ACLITEM_GET_GOPTIONS(*fc_mod_aip) & ~fc_own_privs) != 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_GRANT_OPERATION),
				 errmsg("grant options cannot be granted back to your own grantor")));

	pfree(fc_acl);
}


/*
 * 确保没有特权被“放弃”。如果授予特权的用户失去了授予选项，则特权被放弃。
 * （因此授予的链条被断开。）被放弃的特权要么也被撤销，要么打印错误消息，
 * 具体取决于删除行为选项。
 *
 *	acl：输入的ACL列表
 *	grantee：某些授予选项已被撤销的用户
 *	revoke_privs：被撤销的授予选项
 *	ownerId：对象所有者的Oid
 *	behavior：用于递归删除的限制或级联行为
 *
 * 如果被替换，输入的Acl对象将被pfree。
 */
static Acl * fc_recursive_revoke(Acl *fc_acl,
				 Oid fc_grantee,
				 AclMode fc_revoke_privs,
				 Oid fc_ownerId,
				 DropBehavior fc_behavior)
{
	AclMode		fc_still_has;
	AclItem    *fc_aip;
	int			fc_i,
				fc_num;

	fc_check_acl(fc_acl);

	/* 所有者永远不会真正失去授予选项，因此短路 */
	if (fc_grantee == fc_ownerId)
		return fc_acl;

	/* 受赠者可能仍通过另一个授予者拥有一些授予选项 */
	fc_still_has = aclmask(fc_acl, fc_grantee, fc_ownerId,
						ACL_GRANT_OPTION_FOR(fc_revoke_privs),
						ACLMASK_ALL);
	fc_revoke_privs &= ~ACL_OPTION_TO_PRIVS(fc_still_has);
	if (fc_revoke_privs == ACL_NO_RIGHTS)
		return fc_acl;

restart:
	fc_num = ACL_NUM(fc_acl);
	fc_aip = ACL_DAT(fc_acl);
	for (fc_i = 0; fc_i < fc_num; fc_i++)
	{
		if (fc_aip[fc_i].ai_grantor == fc_grantee
			&& (ACLITEM_GET_PRIVS(fc_aip[fc_i]) & fc_revoke_privs) != 0)
		{
			AclItem		fc_mod_acl;
			Acl		   *fc_new_acl;

			if (fc_behavior == DROP_RESTRICT)
				ereport(ERROR,
						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
						 errmsg("dependent privileges exist"),
						 errhint("Use CASCADE to revoke them too.")));

			fc_mod_acl.ai_grantor = fc_grantee;
			fc_mod_acl.ai_grantee = fc_aip[fc_i].ai_grantee;
			ACLITEM_SET_PRIVS_GOPTIONS(fc_mod_acl,
									   fc_revoke_privs,
									   fc_revoke_privs);

			fc_new_acl = aclupdate(fc_acl, &fc_mod_acl, ACL_MODECHG_DEL,
								fc_ownerId, fc_behavior);

			pfree(fc_acl);
			fc_acl = fc_new_acl;

			goto restart;
		}
	}

	return fc_acl;
}


/*
 * aclmask --- 计算角色ID持有的所有特权的位掩码。
 *
 * 当'how' = ACLMASK_ALL时，简单返回根据给定ACL列表
 * 由给定roleid持有的特权位，与'mask'进行与运算。
 * （传递'mask'的目的是让例程在发现所有感兴趣的特权时尽早退出。）
 *
 * 当'how' = ACLMASK_ANY时，一旦已知掩码中的任何位为真，返回。
 * （这让我们可以在调用者只测试零或非零结果的情况下尽早退出。）
 *
 * 使用模式：
 *
 * 检查是否持有一组特权中的任何一个：
 *		if (aclmask(acl, roleid, ownerId, privs, ACLMASK_ANY) != 0)
 *
 * 检查是否持有一组特权中的所有：
 *		if (aclmask(acl, roleid, ownerId, privs, ACLMASK_ALL) == privs)
 *
 * 确定确切持有哪些特权：
 *		heldprivs = aclmask(acl, roleid, ownerId, privs, ACLMASK_ALL);
 */
AclMode aclmask(const Acl *fc_acl, Oid fc_roleid, Oid fc_ownerId,
		AclMode fc_mask, AclMaskHow fc_how)
{
	AclMode		fc_result;
	AclMode		fc_remaining;
	AclItem    *fc_aidat;
	int			fc_i,
				fc_num;

	/*
	 * 空ACL不应该发生，因为调用者应该插入适当的默认值
	 */
	if (fc_acl == NULL)
		elog(ERROR, "null ACL");

	fc_check_acl(fc_acl);

	/* 对于 mask == 0 的快速退出 */
	if (fc_mask == 0)
		return 0;

	fc_result = 0;

	/* 所有者始终默认为拥有所有授予选项 */
	if ((fc_mask & ACLITEM_ALL_GOPTION_BITS) &&
		has_privs_of_role(fc_roleid, fc_ownerId))
	{
		fc_result = fc_mask & ACLITEM_ALL_GOPTION_BITS;
		if ((fc_how == ACLMASK_ALL) ? (fc_result == fc_mask) : (fc_result != 0))
			return fc_result;
	}

	fc_num = ACL_NUM(fc_acl);
	fc_aidat = ACL_DAT(fc_acl);

	/*
	 * 检查直接授予给roleid或公共的权限
	 */
	for (fc_i = 0; fc_i < fc_num; fc_i++)
	{
		AclItem    *fc_aidata = &fc_aidat[fc_i];

		if (fc_aidata->ai_grantee == ACL_ID_PUBLIC ||
			fc_aidata->ai_grantee == fc_roleid)
		{
			fc_result |= fc_aidata->ai_privs & fc_mask;
			if ((fc_how == ACLMASK_ALL) ? (fc_result == fc_mask) : (fc_result != 0))
				return fc_result;
		}
	}

	/*
	 * 检查通过角色成员身份间接授予的权限。我们在单独的步骤中执行此操作，以最小化昂贵的间接成员身份测试。特别是，在执行has_privs_of_role测试之前，值得测试给定的ACL条目是否授予任何仍然感兴趣的权限。
	 */
	fc_remaining = fc_mask & ~fc_result;
	for (fc_i = 0; fc_i < fc_num; fc_i++)
	{
		AclItem    *fc_aidata = &fc_aidat[fc_i];

		if (fc_aidata->ai_grantee == ACL_ID_PUBLIC ||
			fc_aidata->ai_grantee == fc_roleid)
			continue;			/* 已经检查过了 */

		if ((fc_aidata->ai_privs & fc_remaining) &&
			has_privs_of_role(fc_roleid, fc_aidata->ai_grantee))
		{
			fc_result |= fc_aidata->ai_privs & fc_mask;
			if ((fc_how == ACLMASK_ALL) ? (fc_result == fc_mask) : (fc_result != 0))
				return fc_result;
			fc_remaining = fc_mask & ~fc_result;
		}
	}

	return fc_result;
}


/*
 * aclmask_direct --- 计算roleid所拥有的所有权限的位掩码。
 *
 * 这与aclmask()完全相同，只是我们只考虑由roleid *直接*拥有的权限，而不是通过角色成员身份继承的权限。
 */
static AclMode fc_aclmask_direct(const Acl *fc_acl, Oid fc_roleid, Oid fc_ownerId,
			   AclMode fc_mask, AclMaskHow fc_how)
{
	AclMode		fc_result;
	AclItem    *fc_aidat;
	int			fc_i,
				fc_num;

	/*
	 * 空ACL不应该发生，因为调用者应该插入适当的默认值
	 */
	if (fc_acl == NULL)
		elog(ERROR, "null ACL");

	fc_check_acl(fc_acl);

	/* 对于 mask == 0 的快速退出 */
	if (fc_mask == 0)
		return 0;

	fc_result = 0;

	/* 所有者始终默认为拥有所有授予选项 */
	if ((fc_mask & ACLITEM_ALL_GOPTION_BITS) &&
		fc_roleid == fc_ownerId)
	{
		fc_result = fc_mask & ACLITEM_ALL_GOPTION_BITS;
		if ((fc_how == ACLMASK_ALL) ? (fc_result == fc_mask) : (fc_result != 0))
			return fc_result;
	}

	fc_num = ACL_NUM(fc_acl);
	fc_aidat = ACL_DAT(fc_acl);

	/*
	 * 检查直接授予给roleid的权限（而不是公共的）
	 */
	for (fc_i = 0; fc_i < fc_num; fc_i++)
	{
		AclItem    *fc_aidata = &fc_aidat[fc_i];

		if (fc_aidata->ai_grantee == fc_roleid)
		{
			fc_result |= fc_aidata->ai_privs & fc_mask;
			if ((fc_how == ACLMASK_ALL) ? (fc_result == fc_mask) : (fc_result != 0))
				return fc_result;
		}
	}

	return fc_result;
}


/*
 * aclmembers
 *		查找Acl中提到的所有roleid。
 *		注意，我们不区分授予者和受授者。
 *
 * *roleids被设置为指向一个palloc'd数组，该数组包含按排序顺序排列的不同OID。数组的长度是函数结果。
 */
int aclmembers(const Acl *fc_acl, Oid **fc_roleids)
{
	Oid		   *fc_list;
	const AclItem *fc_acldat;
	int			fc_i,
				fc_j;

	if (fc_acl == NULL || ACL_NUM(fc_acl) == 0)
	{
		*fc_roleids = NULL;
		return 0;
	}

	fc_check_acl(fc_acl);

	/* 分配最坏情况下的空间需求 */
	fc_list = palloc(ACL_NUM(fc_acl) * 2 * sizeof(Oid));
	fc_acldat = ACL_DAT(fc_acl);

	/*
	 * 遍历ACL收集提到的RoleIds。
	 */
	fc_j = 0;
	for (fc_i = 0; fc_i < ACL_NUM(fc_acl); fc_i++)
	{
		const AclItem *fc_ai = &fc_acldat[fc_i];

		if (fc_ai->ai_grantee != ACL_ID_PUBLIC)
			fc_list[fc_j++] = fc_ai->ai_grantee;
		/* 授予者当前从不为PUBLIC，但我们还是检查一下 */
		if (fc_ai->ai_grantor != ACL_ID_PUBLIC)
			fc_list[fc_j++] = fc_ai->ai_grantor;
	}

	/* 对数组进行排序 */
	qsort(fc_list, fc_j, sizeof(Oid), oid_cmp);

	/*
	 * 我们可以将数组重新分配到最小大小，但这几乎没有必要，因为它只是临时内存。
	 */
	*fc_roleids = fc_list;

	/* 从数组中删除重复项 */
	return qunique(fc_list, fc_j, sizeof(Oid), oid_cmp);
}


/*
 * aclinsert（导出函数）
 */
Datum aclinsert(PG_FUNCTION_ARGS)
{
	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("aclinsert is no longer supported")));

	PG_RETURN_NULL();			/* 保持编译器安静 */
}

Datum aclremove(PG_FUNCTION_ARGS)
{
	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("aclremove is no longer supported")));

	PG_RETURN_NULL();			/* 保持编译器安静 */
}

Datum aclcontains(PG_FUNCTION_ARGS)
{
	Acl		   *fc_acl = PG_GETARG_ACL_P(0);
	AclItem    *fc_aip = PG_GETARG_ACLITEM_P(1);
	AclItem    *fc_aidat;
	int			fc_i,
				fc_num;

	fc_check_acl(fc_acl);
	fc_num = ACL_NUM(fc_acl);
	fc_aidat = ACL_DAT(fc_acl);
	for (fc_i = 0; fc_i < fc_num; ++fc_i)
	{
		if (fc_aip->ai_grantee == fc_aidat[fc_i].ai_grantee &&
			fc_aip->ai_grantor == fc_aidat[fc_i].ai_grantor &&
			(ACLITEM_GET_RIGHTS(*fc_aip) & ACLITEM_GET_RIGHTS(fc_aidat[fc_i])) == ACLITEM_GET_RIGHTS(*fc_aip))
			PG_RETURN_BOOL(true);
	}
	PG_RETURN_BOOL(false);
}

Datum makeaclitem(PG_FUNCTION_ARGS)
{
	Oid			fc_grantee = PG_GETARG_OID(0);
	Oid			fc_grantor = PG_GETARG_OID(1);
	text	   *fc_privtext = PG_GETARG_TEXT_PP(2);
	bool		fc_goption = PG_GETARG_BOOL(3);
	AclItem    *fc_result;
	AclMode		fc_priv;

	fc_priv = fc_convert_priv_string(fc_privtext);

	fc_result = (AclItem *) palloc(sizeof(AclItem));

	fc_result->ai_grantee = fc_grantee;
	fc_result->ai_grantor = fc_grantor;

	ACLITEM_SET_PRIVS_GOPTIONS(*fc_result, fc_priv,
							   (fc_goption ? fc_priv : ACL_NO_RIGHTS));

	PG_RETURN_ACLITEM_P(fc_result);
}

static AclMode fc_convert_priv_string(text *fc_priv_type_text)
{
	char	   *fc_priv_type = text_to_cstring(fc_priv_type_text);

	if (pg_strcasecmp(fc_priv_type, "SELECT") == 0)
		return ACL_SELECT;
	if (pg_strcasecmp(fc_priv_type, "INSERT") == 0)
		return ACL_INSERT;
	if (pg_strcasecmp(fc_priv_type, "UPDATE") == 0)
		return ACL_UPDATE;
	if (pg_strcasecmp(fc_priv_type, "DELETE") == 0)
		return ACL_DELETE;
	if (pg_strcasecmp(fc_priv_type, "TRUNCATE") == 0)
		return ACL_TRUNCATE;
	if (pg_strcasecmp(fc_priv_type, "REFERENCES") == 0)
		return ACL_REFERENCES;
	if (pg_strcasecmp(fc_priv_type, "TRIGGER") == 0)
		return ACL_TRIGGER;
	if (pg_strcasecmp(fc_priv_type, "EXECUTE") == 0)
		return ACL_EXECUTE;
	if (pg_strcasecmp(fc_priv_type, "USAGE") == 0)
		return ACL_USAGE;
	if (pg_strcasecmp(fc_priv_type, "CREATE") == 0)
		return ACL_CREATE;
	if (pg_strcasecmp(fc_priv_type, "TEMP") == 0)
		return ACL_CREATE_TEMP;
	if (pg_strcasecmp(fc_priv_type, "TEMPORARY") == 0)
		return ACL_CREATE_TEMP;
	if (pg_strcasecmp(fc_priv_type, "CONNECT") == 0)
		return ACL_CONNECT;
	if (pg_strcasecmp(fc_priv_type, "SET") == 0)
		return ACL_SET;
	if (pg_strcasecmp(fc_priv_type, "ALTER SYSTEM") == 0)
		return ACL_ALTER_SYSTEM;
	if (pg_strcasecmp(fc_priv_type, "RULE") == 0)
		return 0;				/* 忽略旧的 RULE 特权 */

	ereport(ERROR,
			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
			 errmsg("unrecognized privilege type: \"%s\"", fc_priv_type)));
	return ACL_NO_RIGHTS;		/* 保持编译器安静 */
}


/*
 * convert_any_priv_string: 识别has_foo_privilege的权限字符串
 *
 * 我们接受一个不区分大小写的、用逗号分隔的权限名称列表，生成OR'd权限位的位掩码。我们对项之间的空白符持宽松态度，但对项内部的空白符就没那么宽松了。
 * 允许的权限名称以priv_map结构数组的形式给出，数组以一个NULL名称指针结束。
 */
static AclMode fc_convert_any_priv_string(text *fc_priv_type_text,
						const priv_map *fc_privileges)
{
	AclMode		fc_result = 0;
	char	   *fc_priv_type = text_to_cstring(fc_priv_type_text);
	char	   *fc_chunk;
	char	   *fc_next_chunk;

	/* 我们依赖priv_type为私有的、可修改的字符串 */
	for (fc_chunk = fc_priv_type; fc_chunk; fc_chunk = fc_next_chunk)
	{
		int			fc_chunk_len;
		const priv_map *fc_this_priv;

		/* 在逗号处拆分字符串 */
		fc_next_chunk = strchr(fc_chunk, ',');
		if (fc_next_chunk)
			*fc_next_chunk++ = '\0';

		/* 删除此块中的前导/尾随空白 */
		while (*fc_chunk && isspace((unsigned char) *fc_chunk))
			fc_chunk++;
		fc_chunk_len = strlen(fc_chunk);
		while (fc_chunk_len > 0 && isspace((unsigned char) fc_chunk[fc_chunk_len - 1]))
			fc_chunk_len--;
		fc_chunk[fc_chunk_len] = '\0';

		/* 匹配权限列表 */
		for (fc_this_priv = fc_privileges; fc_this_priv->name; fc_this_priv++)
		{
			if (pg_strcasecmp(fc_this_priv->name, fc_chunk) == 0)
			{
				fc_result |= fc_this_priv->value;
				break;
			}
		}
		if (!fc_this_priv->name)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("unrecognized privilege type: \"%s\"", fc_chunk)));
	}

	pfree(fc_priv_type);
	return fc_result;
}


static const char * fc_convert_aclright_to_string(int fc_aclright)
{
	switch (fc_aclright)
	{
		case ACL_INSERT:
			return "INSERT";
		case ACL_SELECT:
			return "SELECT";
		case ACL_UPDATE:
			return "UPDATE";
		case ACL_DELETE:
			return "DELETE";
		case ACL_TRUNCATE:
			return "TRUNCATE";
		case ACL_REFERENCES:
			return "REFERENCES";
		case ACL_TRIGGER:
			return "TRIGGER";
		case ACL_EXECUTE:
			return "EXECUTE";
		case ACL_USAGE:
			return "USAGE";
		case ACL_CREATE:
			return "CREATE";
		case ACL_CREATE_TEMP:
			return "TEMPORARY";
		case ACL_CONNECT:
			return "CONNECT";
		case ACL_SET:
			return "SET";
		case ACL_ALTER_SYSTEM:
			return "ALTER SYSTEM";
		default:
			elog(ERROR, "unrecognized aclright: %d", fc_aclright);
			return NULL;
	}
}


/*----------
 * 将aclitem[]转换为表格。
 *
 * 示例：
 *
 * aclexplode('{=r/joe,foo=a*w/joe}'::aclitem[])
 *
 * 返回的表格
 *
 * {{ OID(joe), 0::OID,   'SELECT', false },
 *	{ OID(joe), OID(foo), 'INSERT', true },
 *	{ OID(joe), OID(foo), 'UPDATE', false }}
 *----------
 */
Datum aclexplode(PG_FUNCTION_ARGS)
{
	Acl		   *fc_acl = PG_GETARG_ACL_P(0);
	FuncCallContext *fc_funcctx;
	int		   *fc_idx;
	AclItem    *fc_aidat;

	if (SRF_IS_FIRSTCALL())
	{
		TupleDesc	fc_tupdesc;
		MemoryContext fc_oldcontext;

		fc_check_acl(fc_acl);

		fc_funcctx = SRF_FIRSTCALL_INIT();
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

		/*
		 * 为结果元组构建tupdesc（与pg_proc条目中的输出参数匹配）
		 */
		fc_tupdesc = CreateTemplateTupleDesc(4);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "grantor",
						   OIDOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "grantee",
						   OIDOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "privilege_type",
						   TEXTOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 4, "is_grantable",
						   BOOLOID, -1, 0);

		fc_funcctx->tuple_desc = BlessTupleDesc(fc_tupdesc);

		/* 为用户上下文分配内存 */
		fc_idx = (int *) palloc(sizeof(int[2]));
		fc_idx[0] = 0;				/* ACL数组项索引 */
		fc_idx[1] = -1;			/* 权限类型计数器 */
		fc_funcctx->user_fctx = (void *) fc_idx;

		MemoryContextSwitchTo(fc_oldcontext);
	}

	fc_funcctx = SRF_PERCALL_SETUP();
	fc_idx = (int *) fc_funcctx->user_fctx;
	fc_aidat = ACL_DAT(fc_acl);

	/* 如果acl没有项目，这里需要测试 */
	while (fc_idx[0] < ACL_NUM(fc_acl))
	{
		AclItem    *fc_aidata;
		AclMode		fc_priv_bit;

		fc_idx[1]++;
		if (fc_idx[1] == N_ACL_RIGHTS)
		{
			fc_idx[1] = 0;
			fc_idx[0]++;
			if (fc_idx[0] >= ACL_NUM(fc_acl)) /* 完成 */
				break;
		}
		fc_aidata = &fc_aidat[fc_idx[0]];
		fc_priv_bit = 1 << fc_idx[1];

		if (ACLITEM_GET_PRIVS(*fc_aidata) & fc_priv_bit)
		{
			Datum		fc_result;
			Datum		fc_values[4];
			bool		fc_nulls[4];
			HeapTuple	fc_tuple;

			fc_values[0] = ObjectIdGetDatum(fc_aidata->ai_grantor);
			fc_values[1] = ObjectIdGetDatum(fc_aidata->ai_grantee);
			fc_values[2] = CStringGetTextDatum(fc_convert_aclright_to_string(fc_priv_bit));
			fc_values[3] = BoolGetDatum((ACLITEM_GET_GOPTIONS(*fc_aidata) & fc_priv_bit) != 0);

			MemSet(fc_nulls, 0, sizeof(fc_nulls));

			fc_tuple = heap_form_tuple(fc_funcctx->tuple_desc, fc_values, fc_nulls);
			fc_result = HeapTupleGetDatum(fc_tuple);

			SRF_RETURN_NEXT(fc_funcctx, fc_result);
		}
	}

	SRF_RETURN_DONE(fc_funcctx);
}


/*
 * has_table_privilege变体
 *		这些在SQL级别都名为"has_table_privilege"。
 *		它们接受关系名称、关系OID、用户名、用户OID或隐式用户 = current_user的各种组合。
 *
 *		结果是一个布尔值：如果用户拥有指示的权限，则为true；否则为false。接受关系OID的变体如果OID不存在，则返回NULL（而不是像Postgres 8.4之前那样失败）。
 */

/*
 * has_table_privilege_name_name
 *		检查给定表的用户权限，表名为username，text tablename和text priv name。
 */
Datum has_table_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_rolename = PG_GETARG_NAME(0);
	text	   *fc_tablename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_tableoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_rolename));
	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_mode = fc_convert_table_priv_string(fc_priv_type_text);

	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_table_privilege_name
 *		检查用户在给定表上的权限
 *		text tablename 和 text priv name。
 *		假定是 current_user
 */
Datum has_table_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_tablename = PG_GETARG_TEXT_PP(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_tableoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_mode = fc_convert_table_priv_string(fc_priv_type_text);

	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_table_privilege_name_id
 *		检查用户在给定表上的权限
 *		name usename、table oid 和 text priv name。
 */
Datum has_table_privilege_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_tableoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_table_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(fc_tableoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_table_privilege_id
 *		检查用户在给定表上的权限
 *		table oid 和 text priv name。
 *		假定是 current_user
 */
Datum has_table_privilege_id(PG_FUNCTION_ARGS)
{
	Oid			fc_tableoid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_table_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(fc_tableoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_table_privilege_id_name
 *		检查用户在给定表上的权限
 *		roleid、text tablename 和 text priv name。
 */
Datum has_table_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_tablename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_tableoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_mode = fc_convert_table_priv_string(fc_priv_type_text);

	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_table_privilege_id_id
 *		检查用户在给定表上的权限
 *		roleid、table oid 和 text priv name。
 */
Datum has_table_privilege_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_tableoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_table_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(fc_tableoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 *		支持 has_table_privilege 家族的例程。
 */

/*
 * 给定一个以字符串表示的表名，查找并返回 Oid
 */
static Oid fc_convert_table_name(text *fc_tablename)
{
	RangeVar   *fc_relrv;

	fc_relrv = makeRangeVarFromNameList(textToQualifiedNameList(fc_tablename));

	/* 我们可能甚至没有此关系的权限；不要锁定它。 */
	return RangeVarGetRelid(fc_relrv, NoLock, false);
}

/*
 * convert_table_priv_string
 *		将文本字符串转换为 AclMode 值。
 */
static AclMode fc_convert_table_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_table_priv_map[] = {
		{"SELECT", ACL_SELECT},
		{"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)},
		{"INSERT", ACL_INSERT},
		{"INSERT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_INSERT)},
		{"UPDATE", ACL_UPDATE},
		{"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)},
		{"DELETE", ACL_DELETE},
		{"DELETE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_DELETE)},
		{"TRUNCATE", ACL_TRUNCATE},
		{"TRUNCATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRUNCATE)},
		{"REFERENCES", ACL_REFERENCES},
		{"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)},
		{"TRIGGER", ACL_TRIGGER},
		{"TRIGGER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRIGGER)},
		{"RULE", 0},			/* 忽略旧的 RULE 特权 */
		{"RULE WITH GRANT OPTION", 0},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_table_priv_map);
}

/*
 * has_sequence_privilege 变体
 *		这些在 SQL 层面上都被命名为 "has_sequence_privilege"。
 *		它们接受各种关系名称、关系 OID、
 *		用户名、用户 OID，或隐式用户 = current_user 的组合。
 *
 *		结果是一个布尔值：如果用户拥有指示的
 *		权限则为 true，否则为 false。接受关系 OID 的变体
 *		如果 OID 不存在，则返回 NULL。
 */

/*
 * has_sequence_privilege_name_name
 *		检查用户在给定序列上的权限
 *		name username、text sequencename 和 text priv name。
 */
Datum has_sequence_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_rolename = PG_GETARG_NAME(0);
	text	   *fc_sequencename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_sequenceoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_rolename));
	fc_mode = fc_convert_sequence_priv_string(fc_priv_type_text);
	fc_sequenceoid = fc_convert_table_name(fc_sequencename);
	if (get_rel_relkind(fc_sequenceoid) != RELKIND_SEQUENCE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a sequence",
						text_to_cstring(fc_sequencename))));

	fc_aclresult = pg_class_aclcheck(fc_sequenceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_sequence_privilege_name
 *		检查用户在给定序列上的权限
 *		text sequencename 和 text priv name。
 *		假定是 current_user
 */
Datum has_sequence_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_sequencename = PG_GETARG_TEXT_PP(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_sequenceoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_sequence_priv_string(fc_priv_type_text);
	fc_sequenceoid = fc_convert_table_name(fc_sequencename);
	if (get_rel_relkind(fc_sequenceoid) != RELKIND_SEQUENCE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a sequence",
						text_to_cstring(fc_sequencename))));

	fc_aclresult = pg_class_aclcheck(fc_sequenceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_sequence_privilege_name_id
 *		检查用户在给定序列上的权限
 *		name usename、sequence oid 和 text priv name。
 */
Datum has_sequence_privilege_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_sequenceoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;
	char		fc_relkind;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_sequence_priv_string(fc_priv_type_text);
	fc_relkind = get_rel_relkind(fc_sequenceoid);
	if (fc_relkind == '\0')
		PG_RETURN_NULL();
	else if (fc_relkind != RELKIND_SEQUENCE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a sequence",
						get_rel_name(fc_sequenceoid))));

	fc_aclresult = pg_class_aclcheck(fc_sequenceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_sequence_privilege_id
 *		检查用户在给定序列上的权限
 *		sequence oid 和 text priv name。
 *		假定是 current_user
 */
Datum has_sequence_privilege_id(PG_FUNCTION_ARGS)
{
	Oid			fc_sequenceoid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;
	char		fc_relkind;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_sequence_priv_string(fc_priv_type_text);
	fc_relkind = get_rel_relkind(fc_sequenceoid);
	if (fc_relkind == '\0')
		PG_RETURN_NULL();
	else if (fc_relkind != RELKIND_SEQUENCE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a sequence",
						get_rel_name(fc_sequenceoid))));

	fc_aclresult = pg_class_aclcheck(fc_sequenceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_sequence_privilege_id_name
 *		检查用户在给定序列上的权限
 *		roleid、text sequencename 和 text priv name。
 */
Datum has_sequence_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_sequencename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_sequenceoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_sequence_priv_string(fc_priv_type_text);
	fc_sequenceoid = fc_convert_table_name(fc_sequencename);
	if (get_rel_relkind(fc_sequenceoid) != RELKIND_SEQUENCE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a sequence",
						text_to_cstring(fc_sequencename))));

	fc_aclresult = pg_class_aclcheck(fc_sequenceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_sequence_privilege_id_id
 *		检查用户在给定序列上的权限
 *		roleid、sequence oid 和 text priv name。
 */
Datum has_sequence_privilege_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_sequenceoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;
	char		fc_relkind;

	fc_mode = fc_convert_sequence_priv_string(fc_priv_type_text);
	fc_relkind = get_rel_relkind(fc_sequenceoid);
	if (fc_relkind == '\0')
		PG_RETURN_NULL();
	else if (fc_relkind != RELKIND_SEQUENCE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a sequence",
						get_rel_name(fc_sequenceoid))));

	fc_aclresult = pg_class_aclcheck(fc_sequenceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * convert_sequence_priv_string
 *		将文本字符串转换为 AclMode 值。
 */
static AclMode fc_convert_sequence_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_sequence_priv_map[] = {
		{"USAGE", ACL_USAGE},
		{"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)},
		{"SELECT", ACL_SELECT},
		{"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)},
		{"UPDATE", ACL_UPDATE},
		{"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_sequence_priv_map);
}


/*
 * has_any_column_privilege 变体
 *		这些在 SQL 层面上都被命名为 "has_any_column_privilege"。
 *		它们接受各种关系名称、关系 OID、
 *		用户名、用户 OID，或隐式用户 = current_user 的组合。
 *
 *		结果是一个布尔值：如果用户在表的任何列上拥有指示的
 *		权限则为 true，否则为 false。接受关系 OID 的变体
 *		如果 OID 不存在，则返回 NULL。
 */

/*
 * has_any_column_privilege_name_name
 *		检查用户在给定表的任何列上的权限
 *		name username、text tablename 和 text priv name。
 */
Datum has_any_column_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_rolename = PG_GETARG_NAME(0);
	text	   *fc_tablename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_tableoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_rolename));
	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	/* 首先在表级别检查，然后在需要时检查每一列 */
	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);
	if (fc_aclresult != ACLCHECK_OK)
		fc_aclresult = pg_attribute_aclcheck_all(fc_tableoid, fc_roleid, fc_mode,
											  ACLMASK_ANY);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_any_column_privilege_name
 *		检查用户在给定表的任何列上的权限
 *		text tablename 和 text priv name。
 *		假定是 current_user
 */
Datum has_any_column_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_tablename = PG_GETARG_TEXT_PP(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_tableoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	/* 首先在表级别检查，然后在需要时检查每一列 */
	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);
	if (fc_aclresult != ACLCHECK_OK)
		fc_aclresult = pg_attribute_aclcheck_all(fc_tableoid, fc_roleid, fc_mode,
											  ACLMASK_ANY);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_any_column_privilege_name_id
 *		检查用户在给定表的任何列上的权限
 *		name usename、table oid 和 text priv name。
 */
Datum has_any_column_privilege_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_tableoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(fc_tableoid)))
		PG_RETURN_NULL();

	/* 首先在表级别检查，然后在需要时检查每一列 */
	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);
	if (fc_aclresult != ACLCHECK_OK)
		fc_aclresult = pg_attribute_aclcheck_all(fc_tableoid, fc_roleid, fc_mode,
											  ACLMASK_ANY);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_any_column_privilege_id
 *		检查用户在给定表的任何列上的权限
 *		table oid 和 text priv name。
 *		假定是 current_user
 */
Datum has_any_column_privilege_id(PG_FUNCTION_ARGS)
{
	Oid			fc_tableoid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(fc_tableoid)))
		PG_RETURN_NULL();

	/* 首先在表级别检查，然后在需要时检查每一列 */
	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);
	if (fc_aclresult != ACLCHECK_OK)
		fc_aclresult = pg_attribute_aclcheck_all(fc_tableoid, fc_roleid, fc_mode,
											  ACLMASK_ANY);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_any_column_privilege_id_name
 *		检查用户在给定表的任何列上的权限
 *		roleid、text tablename 和 text priv name。
 */
Datum has_any_column_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_tablename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_tableoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	/* 首先在表级别检查，然后在需要时检查每一列 */
	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);
	if (fc_aclresult != ACLCHECK_OK)
		fc_aclresult = pg_attribute_aclcheck_all(fc_tableoid, fc_roleid, fc_mode,
											  ACLMASK_ANY);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_any_column_privilege_id_id
 *		检查用户在给定表的任何列上的权限
 *		roleid、table oid 和 text priv name。
 */
Datum has_any_column_privilege_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_tableoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(fc_tableoid)))
		PG_RETURN_NULL();

	/* 首先在表级别检查，然后在需要时检查每一列 */
	fc_aclresult = pg_class_aclcheck(fc_tableoid, fc_roleid, fc_mode);
	if (fc_aclresult != ACLCHECK_OK)
		fc_aclresult = pg_attribute_aclcheck_all(fc_tableoid, fc_roleid, fc_mode,
											  ACLMASK_ANY);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}


/*
 * has_column_privilege 变体
 *		这些在 SQL 层面上都被命名为 "has_column_privilege"。
 *		它们接受各种关系名称、关系 OID、
 *		列名称、列 attnum、用户名、用户 OID，或
 *		隐式用户 = current_user 的组合。
 *
 *		结果是一个布尔值：如果用户拥有指示的
 *		权限则为 true，否则为 false。接受关系 OID 的变体
 *		如果该关系 OID 不存在则返回 NULL（而不是抛出错误）。
 *		同样，接受整数 attnum 的变体
 *		如果没有这样的 pg_attribute 条目，则返回 NULL（而不是抛出错误）。
 *		如果选择了 attisdropped 列，则所有变体返回 NULL。
 *		这些规则旨在避免查询在扫描 pg_attribute 时出现不必要的
 *		失败。
 */

/*
 * column_privilege_check: 检查列权限，但不要对
 *		已删除的列或表抛出错误
 *
 * 如果拥有权限则返回 1，如果没有则返回 0，如果已删除的列/表返回 -1。
 */
static int fc_column_privilege_check(Oid fc_tableoid, AttrNumber fc_attnum,
					   Oid fc_roleid, AclMode fc_mode)
{
	AclResult	fc_aclresult;
	bool		fc_is_missing = false;

	
/*
	 * 如果 convert_column_name 失败，我们可以立即返回 -1。
	 */
	if (fc_attnum == InvalidAttrNumber)
		return -1;

	/*
	 * 首先检查列级权限。这部分也作为一个检查
	 * 列是否存在，所以我们需要在检查
	 * 表级权限之前进行此检查。
	 */
	fc_aclresult = pg_attribute_aclcheck_ext(fc_tableoid, fc_attnum, fc_roleid,
										  fc_mode, &fc_is_missing);
	if (fc_aclresult == ACLCHECK_OK)
		return 1;
	else if (fc_is_missing)
		return -1;

	/* 接下来检查我们是否在表级具有权限 */
	fc_aclresult = pg_class_aclcheck_ext(fc_tableoid, fc_roleid, fc_mode, &fc_is_missing);
	if (fc_aclresult == ACLCHECK_OK)
		return 1;
	else if (fc_is_missing)
		return -1;
	else
		return 0;
}

/*
 * has_column_privilege_name_name_name
 *		检查用户在给定列上的权限
 *		用户名、表名、列名和权限名称。
 */
Datum has_column_privilege_name_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_rolename = PG_GETARG_NAME(0);
	text	   *fc_tablename = PG_GETARG_TEXT_PP(1);
	text	   *fc_column = PG_GETARG_TEXT_PP(2);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(3);
	Oid			fc_roleid;
	Oid			fc_tableoid;
	AttrNumber	fc_colattnum;
	AclMode		fc_mode;
	int			fc_privresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_rolename));
	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_colattnum = fc_convert_column_name(fc_tableoid, fc_column);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 * has_column_privilege_name_name_attnum
 *		检查用户在给定列上的权限
 *		用户名、表名、属性编号和权限名称。
 */
Datum has_column_privilege_name_name_attnum(PG_FUNCTION_ARGS)
{
	Name		fc_rolename = PG_GETARG_NAME(0);
	text	   *fc_tablename = PG_GETARG_TEXT_PP(1);
	AttrNumber	fc_colattnum = PG_GETARG_INT16(2);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(3);
	Oid			fc_roleid;
	Oid			fc_tableoid;
	AclMode		fc_mode;
	int			fc_privresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_rolename));
	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 * has_column_privilege_name_id_name
 *		检查用户在给定列上的权限
 *		用户名、表oid、列名和权限名称。
 */
Datum has_column_privilege_name_id_name(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_tableoid = PG_GETARG_OID(1);
	text	   *fc_column = PG_GETARG_TEXT_PP(2);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(3);
	Oid			fc_roleid;
	AttrNumber	fc_colattnum;
	AclMode		fc_mode;
	int			fc_privresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_colattnum = fc_convert_column_name(fc_tableoid, fc_column);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 * has_column_privilege_name_id_attnum
 *		检查用户在给定列上的权限
 *		用户名、表oid、属性编号和权限名称。
 */
Datum has_column_privilege_name_id_attnum(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_tableoid = PG_GETARG_OID(1);
	AttrNumber	fc_colattnum = PG_GETARG_INT16(2);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(3);
	Oid			fc_roleid;
	AclMode		fc_mode;
	int			fc_privresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 * has_column_privilege_id_name_name
 *		检查用户在给定列上的权限
 *		roleid、表名、列名和权限名称。
 */
Datum has_column_privilege_id_name_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_tablename = PG_GETARG_TEXT_PP(1);
	text	   *fc_column = PG_GETARG_TEXT_PP(2);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(3);
	Oid			fc_tableoid;
	AttrNumber	fc_colattnum;
	AclMode		fc_mode;
	int			fc_privresult;

	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_colattnum = fc_convert_column_name(fc_tableoid, fc_column);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 * has_column_privilege_id_name_attnum
 *		检查用户在给定列上的权限
 *		roleid、表名、属性编号和权限名称。
 */
Datum has_column_privilege_id_name_attnum(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_tablename = PG_GETARG_TEXT_PP(1);
	AttrNumber	fc_colattnum = PG_GETARG_INT16(2);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(3);
	Oid			fc_tableoid;
	AclMode		fc_mode;
	int			fc_privresult;

	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 * has_column_privilege_id_id_name
 *		检查用户在给定列上的权限
 *		roleid、表oid、列名和权限名称。
 */
Datum has_column_privilege_id_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_tableoid = PG_GETARG_OID(1);
	text	   *fc_column = PG_GETARG_TEXT_PP(2);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(3);
	AttrNumber	fc_colattnum;
	AclMode		fc_mode;
	int			fc_privresult;

	fc_colattnum = fc_convert_column_name(fc_tableoid, fc_column);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 * has_column_privilege_id_id_attnum
 *		检查用户在给定列上的权限
 *		roleid、表oid、属性编号和权限名称。
 */
Datum has_column_privilege_id_id_attnum(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_tableoid = PG_GETARG_OID(1);
	AttrNumber	fc_colattnum = PG_GETARG_INT16(2);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(3);
	AclMode		fc_mode;
	int			fc_privresult;

	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 * has_column_privilege_name_name
 *		检查用户在给定列上的权限
 *		表名、列名和权限名称。
 *		假定当前用户
 */
Datum has_column_privilege_name_name(PG_FUNCTION_ARGS)
{
	text	   *fc_tablename = PG_GETARG_TEXT_PP(0);
	text	   *fc_column = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_tableoid;
	AttrNumber	fc_colattnum;
	AclMode		fc_mode;
	int			fc_privresult;

	fc_roleid = GetUserId();
	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_colattnum = fc_convert_column_name(fc_tableoid, fc_column);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 * has_column_privilege_name_attnum
 *		检查用户在给定列上的权限
 *		表名、属性编号和权限名称。
 *		假定当前用户
 */
Datum has_column_privilege_name_attnum(PG_FUNCTION_ARGS)
{
	text	   *fc_tablename = PG_GETARG_TEXT_PP(0);
	AttrNumber	fc_colattnum = PG_GETARG_INT16(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_tableoid;
	AclMode		fc_mode;
	int			fc_privresult;

	fc_roleid = GetUserId();
	fc_tableoid = fc_convert_table_name(fc_tablename);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 * has_column_privilege_id_name
 *		检查用户在给定列上的权限
 *		表oid、列名和权限名称。
 *		假定当前用户
 */
Datum has_column_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_tableoid = PG_GETARG_OID(0);
	text	   *fc_column = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AttrNumber	fc_colattnum;
	AclMode		fc_mode;
	int			fc_privresult;

	fc_roleid = GetUserId();
	fc_colattnum = fc_convert_column_name(fc_tableoid, fc_column);
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 * has_column_privilege_id_attnum
 *		检查用户在给定列上的权限
 *		表oid、属性编号和权限名称。
 *		假定当前用户
 */
Datum has_column_privilege_id_attnum(PG_FUNCTION_ARGS)
{
	Oid			fc_tableoid = PG_GETARG_OID(0);
	AttrNumber	fc_colattnum = PG_GETARG_INT16(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	int			fc_privresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_column_priv_string(fc_priv_type_text);

	fc_privresult = fc_column_privilege_check(fc_tableoid, fc_colattnum, fc_roleid, fc_mode);
	if (fc_privresult < 0)
		PG_RETURN_NULL();
	PG_RETURN_BOOL(fc_privresult);
}

/*
 *		has_column_privilege系列的支持例程。
 */

/*
 * 给定一个表OID和列名（作为字符串表示），查找它并返回列号。
 * 在调用者应返回NULL而不是失败的情况下返回InvalidAttrNumber。
 */
static AttrNumber
fc_convert_column_name(Oid fc_tableoid, text *fc_column)
{
	char	   *fc_colname;
	HeapTuple	fc_attTuple;
	AttrNumber	fc_attnum;

	fc_colname = text_to_cstring(fc_column);

	/*
	 * 我们在这里不使用get_attnum()，因为它会报告已删除的列不存在。
	 * 我们需要将已删除的列与不存在的列区别对待。
	 */
	fc_attTuple = SearchSysCache2(ATTNAME,
							   ObjectIdGetDatum(fc_tableoid),
							   CStringGetDatum(fc_colname));
	if (HeapTupleIsValid(fc_attTuple))
	{
		Form_pg_attribute fc_attributeForm;

		fc_attributeForm = (Form_pg_attribute) GETSTRUCT(fc_attTuple);
		/* 我们想要为已删除的列返回NULL */
		if (fc_attributeForm->attisdropped)
			fc_attnum = InvalidAttrNumber;
		else
			fc_attnum = fc_attributeForm->attnum;
		ReleaseSysCache(fc_attTuple);
	}
	else
	{
		char	   *fc_tablename = get_rel_name(fc_tableoid);

		/*
		 * 如果表OID无效，或它刚刚被删除，我们将得到
		 * NULL。在这种情况下，我们希望has_column_privilege也返回
		 * NULL，因此只返回InvalidAttrNumber。
		 */
		if (fc_tablename != NULL)
		{
			/* tableoid存在，colname不存在，因此抛出错误 */
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column \"%s\" of relation \"%s\" does not exist",
							fc_colname, fc_tablename)));
		}
		/* tableoid不存在，因此像attisdropped情况一样处理 */
		fc_attnum = InvalidAttrNumber;
	}

	pfree(fc_colname);
	return fc_attnum;
}

/*
 * convert_column_priv_string
 *		将文本字符串转换为AclMode值。
 */
static AclMode fc_convert_column_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_column_priv_map[] = {
		{"SELECT", ACL_SELECT},
		{"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)},
		{"INSERT", ACL_INSERT},
		{"INSERT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_INSERT)},
		{"UPDATE", ACL_UPDATE},
		{"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)},
		{"REFERENCES", ACL_REFERENCES},
		{"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_column_priv_map);
}


/*
 * has_database_privilege变体
 *		这些在SQL级别上都被称为"has_database_privilege"。
 *		它们接受数据库名称、数据库OID、用户名、用户OID或隐式用户 = current_user的各种组合。
 *
 *		结果是一个布尔值：如果用户具有指示的权限，则为true，如果没有则为false，如果对象不存在则为NULL。
 */

/*
 * has_database_privilege_name_name
 *		检查用户在给定数据库上的权限
 *		用户名、数据库名称和权限名称。
 */
Datum has_database_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	text	   *fc_databasename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_databaseoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_databaseoid = fc_convert_database_name(fc_databasename);
	fc_mode = fc_convert_database_priv_string(fc_priv_type_text);

	fc_aclresult = pg_database_aclcheck(fc_databaseoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_database_privilege_name
 *		检查用户在给定数据库上的权限
 *		数据库名称和权限名称。
 *		假定当前用户
 */
Datum has_database_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_databasename = PG_GETARG_TEXT_PP(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_databaseoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_databaseoid = fc_convert_database_name(fc_databasename);
	fc_mode = fc_convert_database_priv_string(fc_priv_type_text);

	fc_aclresult = pg_database_aclcheck(fc_databaseoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_database_privilege_name_id
 *		检查用户在给定数据库上的权限
 *		用户名、数据库oid和权限名称。
 */
Datum has_database_privilege_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_databaseoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_database_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(fc_databaseoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_database_aclcheck(fc_databaseoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_database_privilege_id
 *		检查用户在给定数据库上的权限
 *		数据库oid和权限名称。
 *		假定当前用户
 */
Datum has_database_privilege_id(PG_FUNCTION_ARGS)
{
	Oid			fc_databaseoid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_database_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(fc_databaseoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_database_aclcheck(fc_databaseoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_database_privilege_id_name
 *		检查用户在给定数据库上的权限
 *		roleid、数据库名称和权限名称。
 */
Datum has_database_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_databasename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_databaseoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_databaseoid = fc_convert_database_name(fc_databasename);
	fc_mode = fc_convert_database_priv_string(fc_priv_type_text);

	fc_aclresult = pg_database_aclcheck(fc_databaseoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_database_privilege_id_id
 *		检查用户在给定数据库上的权限
 *		roleid、数据库oid和权限名称。
 */
Datum has_database_privilege_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_databaseoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_database_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(fc_databaseoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_database_aclcheck(fc_databaseoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 *		has_database_privilege系列的支持例程。
 */

/*
 * 给定一个数据库名称（以字符串表示），查找它并返回Oid
 */
static Oid fc_convert_database_name(text *fc_databasename)
{
	char	   *fc_dbname = text_to_cstring(fc_databasename);

	return get_database_oid(fc_dbname, false);
}

/*
 * convert_database_priv_string
 *		将文本字符串转换为AclMode值。
 */
static AclMode fc_convert_database_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_database_priv_map[] = {
		{"CREATE", ACL_CREATE},
		{"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
		{"TEMPORARY", ACL_CREATE_TEMP},
		{"TEMPORARY WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP)},
		{"TEMP", ACL_CREATE_TEMP},
		{"TEMP WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE_TEMP)},
		{"CONNECT", ACL_CONNECT},
		{"CONNECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CONNECT)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_database_priv_map);
}


/*
 * has_foreign_data_wrapper_privilege 变体
 *		这些在 SQL 级别上都被命名为 "has_foreign_data_wrapper_privilege"。
 *		它们接受多种组合的外部数据包装器名称、
 *		fdw OID、用户名、用户 OID 或隐式用户 = current_user。
 *
 *		结果是一个布尔值：如果用户拥有指示的
 *		特权则为 true，反之为 false。
 */

/*
 * has_foreign_data_wrapper_privilege_name_name
 *		检查用户在给定用户名、文本 fdwname 和文本 priv 名称的外部数据包装器上的权限。
 */
Datum has_foreign_data_wrapper_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	text	   *fc_fdwname = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_fdwid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_fdwid = fc_convert_foreign_data_wrapper_name(fc_fdwname);
	fc_mode = fc_convert_foreign_data_wrapper_priv_string(fc_priv_type_text);

	fc_aclresult = pg_foreign_data_wrapper_aclcheck(fc_fdwid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_foreign_data_wrapper_privilege_name
 *		检查用户在给定文本 fdwname 和文本 priv 名称的外部数据包装器上的权限。
 *		假设为 current_user
 */
Datum has_foreign_data_wrapper_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_fdwname = PG_GETARG_TEXT_PP(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_fdwid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_fdwid = fc_convert_foreign_data_wrapper_name(fc_fdwname);
	fc_mode = fc_convert_foreign_data_wrapper_priv_string(fc_priv_type_text);

	fc_aclresult = pg_foreign_data_wrapper_aclcheck(fc_fdwid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_foreign_data_wrapper_privilege_name_id
 *		检查用户在给定用户名、外部数据包装器 oid 和文本 priv 名称的外部数据包装器上的权限。
 */
Datum has_foreign_data_wrapper_privilege_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_fdwid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_foreign_data_wrapper_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fc_fdwid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_foreign_data_wrapper_aclcheck(fc_fdwid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_foreign_data_wrapper_privilege_id
 *		检查用户在给定外部数据包装器 oid 和文本 priv 名称的外部数据包装器上的权限。
 *		假设为 current_user
 */
Datum has_foreign_data_wrapper_privilege_id(PG_FUNCTION_ARGS)
{
	Oid			fc_fdwid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_foreign_data_wrapper_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fc_fdwid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_foreign_data_wrapper_aclcheck(fc_fdwid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_foreign_data_wrapper_privilege_id_name
 *		检查用户在给定 roleid、文本 fdwname 和文本 priv 名称的外部数据包装器上的权限。
 */
Datum has_foreign_data_wrapper_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_fdwname = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_fdwid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_fdwid = fc_convert_foreign_data_wrapper_name(fc_fdwname);
	fc_mode = fc_convert_foreign_data_wrapper_priv_string(fc_priv_type_text);

	fc_aclresult = pg_foreign_data_wrapper_aclcheck(fc_fdwid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_foreign_data_wrapper_privilege_id_id
 *		检查用户在给定 roleid、fdw oid 和文本 priv 名称的外部数据包装器上的权限。
 */
Datum has_foreign_data_wrapper_privilege_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_fdwid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_foreign_data_wrapper_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fc_fdwid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_foreign_data_wrapper_aclcheck(fc_fdwid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 *		has_foreign_data_wrapper_privilege 家族的支持例程。
 */

/*
 * 给定作为字符串表示的 FDW 名称，查找并返回 Oid
 */
static Oid fc_convert_foreign_data_wrapper_name(text *fc_fdwname)
{
	char	   *fc_fdwstr = text_to_cstring(fc_fdwname);

	return get_foreign_data_wrapper_oid(fc_fdwstr, false);
}

/*
 * convert_foreign_data_wrapper_priv_string
 *		将文本字符串转换为 AclMode 值。
 */
static AclMode fc_convert_foreign_data_wrapper_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_foreign_data_wrapper_priv_map[] = {
		{"USAGE", ACL_USAGE},
		{"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_foreign_data_wrapper_priv_map);
}


/*
 * has_function_privilege 变体
 *		这些在 SQL 级别上都被命名为 "has_function_privilege"。
 *		它们接受多种组合的函数名称、函数 OID、
 *		用户名、用户 OID 或隐式用户 = current_user。
 *
 *		结果是一个布尔值：如果用户拥有指示的
 *		特权则为 true，反之为 false，或 NULL 如果对象不存在。
 */

/*
 * has_function_privilege_name_name
 *		检查用户在给定用户名、文本 functionname 和文本 priv 名称的函数上的权限。
 */
Datum has_function_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	text	   *fc_functionname = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_functionoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_functionoid = fc_convert_function_name(fc_functionname);
	fc_mode = fc_convert_function_priv_string(fc_priv_type_text);

	fc_aclresult = pg_proc_aclcheck(fc_functionoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_function_privilege_name
 *		检查用户在给定文本 functionname 和文本 priv 名称的函数上的权限。
 *		假设为 current_user
 */
Datum has_function_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_functionname = PG_GETARG_TEXT_PP(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_functionoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_functionoid = fc_convert_function_name(fc_functionname);
	fc_mode = fc_convert_function_priv_string(fc_priv_type_text);

	fc_aclresult = pg_proc_aclcheck(fc_functionoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_function_privilege_name_id
 *		检查用户在给定用户名、函数 oid 和文本 priv 名称的函数上的权限。
 */
Datum has_function_privilege_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_functionoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_function_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(fc_functionoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_proc_aclcheck(fc_functionoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_function_privilege_id
 *		检查用户在给定函数 oid 和文本 priv 名称的函数上的权限。
 *		假设为 current_user
 */
Datum has_function_privilege_id(PG_FUNCTION_ARGS)
{
	Oid			fc_functionoid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_function_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(fc_functionoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_proc_aclcheck(fc_functionoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_function_privilege_id_name
 *		检查用户在给定 roleid、文本 functionname 和文本 priv 名称的函数上的权限。
 */
Datum has_function_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_functionname = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_functionoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_functionoid = fc_convert_function_name(fc_functionname);
	fc_mode = fc_convert_function_priv_string(fc_priv_type_text);

	fc_aclresult = pg_proc_aclcheck(fc_functionoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_function_privilege_id_id
 *		检查用户在给定 roleid、函数 oid 和文本 priv 名称的函数上的权限。
 */
Datum has_function_privilege_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_functionoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_function_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(fc_functionoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_proc_aclcheck(fc_functionoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 *		has_function_privilege 家族的支持例程。
 */

/*
 * 给定作为字符串表示的函数名称，查找并返回 Oid
 */
static Oid fc_convert_function_name(text *fc_functionname)
{
	char	   *fc_funcname = text_to_cstring(fc_functionname);
	Oid			fc_oid;

	fc_oid = DatumGetObjectId(DirectFunctionCall1(regprocedurein,
											   CStringGetDatum(fc_funcname)));

	if (!OidIsValid(fc_oid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function \"%s\" does not exist", fc_funcname)));

	return fc_oid;
}

/*
 * convert_function_priv_string
 *		将文本字符串转换为 AclMode 值。
 */
static AclMode fc_convert_function_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_function_priv_map[] = {
		{"EXECUTE", ACL_EXECUTE},
		{"EXECUTE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_EXECUTE)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_function_priv_map);
}


/*
 * has_language_privilege 变体
 *		这些在 SQL 级别上都被命名为 "has_language_privilege"。
 *		它们接受多种组合的语言名称、语言 OID、
 *		用户名、用户 OID 或隐式用户 = current_user。
 *
 *		结果是一个布尔值：如果用户拥有指示的
 *		特权则为 true，反之为 false，或 NULL 如果对象不存在。
 */

/*
 * has_language_privilege_name_name
 *		检查用户在给定用户名、文本 languagename 和文本 priv 名称的语言上的权限。
 */
Datum has_language_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	text	   *fc_languagename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_languageoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_languageoid = fc_convert_language_name(fc_languagename);
	fc_mode = fc_convert_language_priv_string(fc_priv_type_text);

	fc_aclresult = pg_language_aclcheck(fc_languageoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_language_privilege_name
 *		检查用户在给定文本 languagename 和文本 priv 名称的语言上的权限。
 *		假设为 current_user
 */
Datum has_language_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_languagename = PG_GETARG_TEXT_PP(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_languageoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_languageoid = fc_convert_language_name(fc_languagename);
	fc_mode = fc_convert_language_priv_string(fc_priv_type_text);

	fc_aclresult = pg_language_aclcheck(fc_languageoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_language_privilege_name_id
 *		检查用户在给定用户名、语言 oid 和文本 priv 名称的语言上的权限。
 */
Datum has_language_privilege_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_languageoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_language_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(LANGOID, ObjectIdGetDatum(fc_languageoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_language_aclcheck(fc_languageoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_language_privilege_id
 *		检查用户在给定语言 oid 和文本 priv 名称的语言上的权限。
 *		假设为 current_user
 */
Datum has_language_privilege_id(PG_FUNCTION_ARGS)
{
	Oid			fc_languageoid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_language_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(LANGOID, ObjectIdGetDatum(fc_languageoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_language_aclcheck(fc_languageoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_language_privilege_id_name
 *		检查用户在给定 roleid、文本 languagename 和文本 priv 名称的语言上的权限。
 */
Datum has_language_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_languagename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_languageoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_languageoid = fc_convert_language_name(fc_languagename);
	fc_mode = fc_convert_language_priv_string(fc_priv_type_text);

	fc_aclresult = pg_language_aclcheck(fc_languageoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_language_privilege_id_id
 *		检查用户在给定 roleid、语言 oid 和文本 priv 名称的语言上的权限。
 */
Datum has_language_privilege_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_languageoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_language_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(LANGOID, ObjectIdGetDatum(fc_languageoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_language_aclcheck(fc_languageoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 *		has_language_privilege 家族的支持例程。
 */

/*
 * 给定作为字符串表示的语言名称，查找并返回 Oid
 */
static Oid fc_convert_language_name(text *fc_languagename)
{
	char	   *fc_langname = text_to_cstring(fc_languagename);

	return get_language_oid(fc_langname, false);
}

/*
 * convert_language_priv_string
 *		将文本字符串转换为AclMode值。
 */
static AclMode fc_convert_language_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_language_priv_map[] = {
		{"USAGE", ACL_USAGE},
		{"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_language_priv_map);
}



/*
 * has_schema_privilege variants
 *		这些在 SQL 层面上都被称为 "has_schema_privilege"。
 *		它们接受各种组合的模式名称、模式 OID、
 *		用户名、用户 OID，或隐式用户 = current_user。
 *
 *		结果是一个布尔值：如果用户拥有指定的
 *		权限，则为 true；如果没有，则为 false；如果对象不存在，则为 NULL。
 */

/*
 * has_schema_privilege_name_name
 *		检查用户在给定
 *		名称用户名、文本模式名称和文本权限名称的模式上的权限。
 */
Datum has_schema_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	text	   *fc_schemaname = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_schemaoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_schemaoid = fc_convert_schema_name(fc_schemaname);
	fc_mode = fc_convert_schema_priv_string(fc_priv_type_text);

	fc_aclresult = pg_namespace_aclcheck(fc_schemaoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_schema_privilege_name
 *		检查用户在给定
 *		文本模式名称和文本权限名称的模式上的权限。
 *		假定是 current_user
 */
Datum has_schema_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_schemaname = PG_GETARG_TEXT_PP(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_schemaoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_schemaoid = fc_convert_schema_name(fc_schemaname);
	fc_mode = fc_convert_schema_priv_string(fc_priv_type_text);

	fc_aclresult = pg_namespace_aclcheck(fc_schemaoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_schema_privilege_name_id
 *		检查用户在给定
 *		名称用户名、模式 OID 和文本权限名称的模式上的权限。
 */
Datum has_schema_privilege_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_schemaoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_schema_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(fc_schemaoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_namespace_aclcheck(fc_schemaoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_schema_privilege_id
 *		检查用户在给定
 *		模式 OID 和文本权限名称的模式上的权限。
 *		假定是 current_user
 */
Datum has_schema_privilege_id(PG_FUNCTION_ARGS)
{
	Oid			fc_schemaoid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_schema_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(fc_schemaoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_namespace_aclcheck(fc_schemaoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_schema_privilege_id_name
 *		检查用户在给定
 *		roleid、文本模式名称和文本权限名称的模式上的权限。
 */
Datum has_schema_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_schemaname = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_schemaoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_schemaoid = fc_convert_schema_name(fc_schemaname);
	fc_mode = fc_convert_schema_priv_string(fc_priv_type_text);

	fc_aclresult = pg_namespace_aclcheck(fc_schemaoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_schema_privilege_id_id
 *		检查用户在给定
 *		roleid、模式 OID 和文本权限名称的模式上的权限。
 */
Datum has_schema_privilege_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_schemaoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_schema_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(fc_schemaoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_namespace_aclcheck(fc_schemaoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 *		支持 has_schema_privilege 系列的例程。
 */

/*
 * 给定作为字符串表示的模式名称，查找并返回 Oid
 */
static Oid fc_convert_schema_name(text *fc_schemaname)
{
	char	   *fc_nspname = text_to_cstring(fc_schemaname);

	return get_namespace_oid(fc_nspname, false);
}

/*
 * convert_schema_priv_string
 *		将文本字符串转换为 AclMode 值。
 */
static AclMode fc_convert_schema_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_schema_priv_map[] = {
		{"CREATE", ACL_CREATE},
		{"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
		{"USAGE", ACL_USAGE},
		{"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_schema_priv_map);
}


/*
 * has_server_privilege variants
 *		这些在 SQL 层面上都被称为 "has_server_privilege"。
 *		它们接受各种组合的外部服务器名称、
 *		服务器 OID、用户名、用户 OID，或隐式用户 = current_user。
 *
 *		结果是一个布尔值：如果用户拥有指定的
 *		权限，则为 true；如果没有，则为 false。
 */

/*
 * has_server_privilege_name_name
 *		检查用户在给定
 *		名称用户名、文本服务器名称和文本权限名称的外部服务器上的权限。
 */
Datum has_server_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	text	   *fc_servername = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_serverid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_serverid = fc_convert_server_name(fc_servername);
	fc_mode = fc_convert_server_priv_string(fc_priv_type_text);

	fc_aclresult = pg_foreign_server_aclcheck(fc_serverid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_server_privilege_name
 *		检查用户在给定
 *		文本服务器名称和文本权限名称的外部服务器上的权限。
 *		假定是 current_user
 */
Datum has_server_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_servername = PG_GETARG_TEXT_PP(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_serverid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_serverid = fc_convert_server_name(fc_servername);
	fc_mode = fc_convert_server_priv_string(fc_priv_type_text);

	fc_aclresult = pg_foreign_server_aclcheck(fc_serverid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_server_privilege_name_id
 *		检查用户在给定
 *		名称用户名、外部服务器 OID 和文本权限名称的外部服务器上的权限。
 */
Datum has_server_privilege_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_serverid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_server_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(fc_serverid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_foreign_server_aclcheck(fc_serverid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_server_privilege_id
 *		检查用户在给定
 *		服务器 OID 和文本权限名称的外部服务器上的权限。
 *		假定是 current_user
 */
Datum has_server_privilege_id(PG_FUNCTION_ARGS)
{
	Oid			fc_serverid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_server_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(fc_serverid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_foreign_server_aclcheck(fc_serverid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_server_privilege_id_name
 *		检查用户在给定
 *		roleid、文本服务器名称和文本权限名称的外部服务器上的权限。
 */
Datum has_server_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_servername = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_serverid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_serverid = fc_convert_server_name(fc_servername);
	fc_mode = fc_convert_server_priv_string(fc_priv_type_text);

	fc_aclresult = pg_foreign_server_aclcheck(fc_serverid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_server_privilege_id_id
 *		检查用户在给定
 *		roleid、服务器 OID 和文本权限名称的外部服务器上的权限。
 */
Datum has_server_privilege_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_serverid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_server_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(FOREIGNSERVEROID, ObjectIdGetDatum(fc_serverid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_foreign_server_aclcheck(fc_serverid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 *		支持 has_server_privilege 系列的例程。
 */

/*
 * 给定作为字符串表示的服务器名称，查找并返回 Oid
 */
static Oid fc_convert_server_name(text *fc_servername)
{
	char	   *fc_serverstr = text_to_cstring(fc_servername);

	return get_foreign_server_oid(fc_serverstr, false);
}

/*
 * convert_server_priv_string
 *		将文本字符串转换为 AclMode 值。
 */
static AclMode fc_convert_server_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_server_priv_map[] = {
		{"USAGE", ACL_USAGE},
		{"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_server_priv_map);
}


/*
 * has_tablespace_privilege variants
 *		这些在 SQL 层面上都被称为 "has_tablespace_privilege"。
 *		它们接受各种组合的表空间名称、表空间 OID、
 *		用户名、用户 OID，或隐式用户 = current_user。
 *
 *		结果是一个布尔值：如果用户拥有指定的
 *		权限，则为 true；如果没有，则为 false。
 */

/*
 * has_tablespace_privilege_name_name
 *		检查用户在给定
 *		名称用户名、文本表空间名称和文本权限名称的表空间上的权限。
 */
Datum has_tablespace_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	text	   *fc_tablespacename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_tablespaceoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_tablespaceoid = fc_convert_tablespace_name(fc_tablespacename);
	fc_mode = fc_convert_tablespace_priv_string(fc_priv_type_text);

	fc_aclresult = pg_tablespace_aclcheck(fc_tablespaceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_tablespace_privilege_name
 *		检查用户在给定
 *		文本表空间名称和文本权限名称的表空间上的权限。
 *		假定是 current_user
 */
Datum has_tablespace_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_tablespacename = PG_GETARG_TEXT_PP(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_tablespaceoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_tablespaceoid = fc_convert_tablespace_name(fc_tablespacename);
	fc_mode = fc_convert_tablespace_priv_string(fc_priv_type_text);

	fc_aclresult = pg_tablespace_aclcheck(fc_tablespaceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_tablespace_privilege_name_id
 *		检查用户在给定
 *		名称用户名、表空间 OID 和文本权限名称的表空间上的权限。
 */
Datum has_tablespace_privilege_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_tablespaceoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_tablespace_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(fc_tablespaceoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_tablespace_aclcheck(fc_tablespaceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_tablespace_privilege_id
 *		检查用户在给定
 *		表空间 OID 和文本权限名称的表空间上的权限。
 *		假定是 current_user
 */
Datum has_tablespace_privilege_id(PG_FUNCTION_ARGS)
{
	Oid			fc_tablespaceoid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_tablespace_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(fc_tablespaceoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_tablespace_aclcheck(fc_tablespaceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_tablespace_privilege_id_name
 *		检查用户在给定
 *		roleid、文本表空间名称和文本权限名称的表空间上的权限。
 */
Datum has_tablespace_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_tablespacename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_tablespaceoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_tablespaceoid = fc_convert_tablespace_name(fc_tablespacename);
	fc_mode = fc_convert_tablespace_priv_string(fc_priv_type_text);

	fc_aclresult = pg_tablespace_aclcheck(fc_tablespaceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_tablespace_privilege_id_id
 *		检查用户在给定
 *		roleid、表空间 OID 和文本权限名称的表空间上的权限。
 */
Datum has_tablespace_privilege_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_tablespaceoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_tablespace_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(fc_tablespaceoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_tablespace_aclcheck(fc_tablespaceoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 *		支持 has_tablespace_privilege 系列的例程。
 */

/*
 * 给定作为字符串表示的表空间名称，查找并返回 Oid
 */
static Oid fc_convert_tablespace_name(text *fc_tablespacename)
{
	char	   *fc_spcname = text_to_cstring(fc_tablespacename);

	return get_tablespace_oid(fc_spcname, false);
}

/*
 * convert_tablespace_priv_string
 *		将文本字符串转换为 AclMode 值。
 */
static AclMode fc_convert_tablespace_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_tablespace_priv_map[] = {
		{"CREATE", ACL_CREATE},
		{"CREATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_tablespace_priv_map);
}

/*
 * has_type_privilege 变体
 *		在 SQL 级别，这些都被命名为 "has_type_privilege"。
 *		它们接受类型名称、类型 OID、用户名、用户 OID 或隐含用户 = current_user 的各种组合。
 *
 *		结果是一个布尔值：如果用户拥有所指示的特权，则为 true；如果没有，则为 false；如果对象不存在，则为 NULL。
 */

/*
 * has_type_privilege_name_name
 *		检查用户在给定名称用户名、文本类型名称和文本特权名称的类型上的特权。
 */
Datum has_type_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	text	   *fc_typename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_typeoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_typeoid = fc_convert_type_name(fc_typename);
	fc_mode = fc_convert_type_priv_string(fc_priv_type_text);

	fc_aclresult = pg_type_aclcheck(fc_typeoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_type_privilege_name
 *		检查用户在给定文本类型名称和文本特权名称的类型上的特权。
 *		假定为当前用户
 */
Datum has_type_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_typename = PG_GETARG_TEXT_PP(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_typeoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_typeoid = fc_convert_type_name(fc_typename);
	fc_mode = fc_convert_type_priv_string(fc_priv_type_text);

	fc_aclresult = pg_type_aclcheck(fc_typeoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_type_privilege_name_id
 *		检查用户在给定用户名、类型 OID 和文本特权名称的类型上的特权。
 */
Datum has_type_privilege_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_typeoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid_or_public(NameStr(*fc_username));
	fc_mode = fc_convert_type_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(TYPEOID, ObjectIdGetDatum(fc_typeoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_type_aclcheck(fc_typeoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_type_privilege_id
 *		检查用户在给定类型 OID 和文本特权名称的类型上的特权。
 *		假定为当前用户
 */
Datum has_type_privilege_id(PG_FUNCTION_ARGS)
{
	Oid			fc_typeoid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_type_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(TYPEOID, ObjectIdGetDatum(fc_typeoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_type_aclcheck(fc_typeoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_type_privilege_id_name
 *		检查用户在给定角色 ID、文本类型名称和文本特权名称的类型上的特权。
 */
Datum has_type_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_typename = PG_GETARG_TEXT_PP(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_typeoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_typeoid = fc_convert_type_name(fc_typename);
	fc_mode = fc_convert_type_priv_string(fc_priv_type_text);

	fc_aclresult = pg_type_aclcheck(fc_typeoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * has_type_privilege_id_id
 *		检查用户在给定角色 ID、类型 OID 和文本特权名称的类型上的特权。
 */
Datum has_type_privilege_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_typeoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_type_priv_string(fc_priv_type_text);

	if (!SearchSysCacheExists1(TYPEOID, ObjectIdGetDatum(fc_typeoid)))
		PG_RETURN_NULL();

	fc_aclresult = pg_type_aclcheck(fc_typeoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 *		has_type_privilege 家族的支持例程。
 */

/*
 * 给定作为字符串表示的类型名称，查找并返回 Oid
 */
static Oid fc_convert_type_name(text *fc_typename)
{
	char	   *fc_typname = text_to_cstring(fc_typename);
	Oid			fc_oid;

	fc_oid = DatumGetObjectId(DirectFunctionCall1(regtypein,
											   CStringGetDatum(fc_typname)));

	if (!OidIsValid(fc_oid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("type \"%s\" does not exist", fc_typname)));

	return fc_oid;
}

/*
 * convert_type_priv_string
 *		将文本字符串转换为 AclMode 值。
 */
static AclMode fc_convert_type_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_type_priv_map[] = {
		{"USAGE", ACL_USAGE},
		{"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_USAGE)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_type_priv_map);
}

/*
 * has_parameter_privilege 变体
 *		在 SQL 级别，这些都被命名为 "has_parameter_privilege"。
 *		它们接受参数名称与用户名、用户 OID 或隐含用户 = current_user 的各种组合。
 *
 *		结果是一个布尔值：如果用户获得了所指示的特权则为 true，反之为 false。
 */

/*
 * has_param_priv_byname
 *
 *		辅助函数，用于检查给定角色 Oid、文本名称的参数和 AclMode 的特权下用户对参数的特权。
 */
static bool fc_has_param_priv_byname(Oid fc_roleid, const text *fc_parameter, AclMode fc_priv)
{
	char	   *fc_paramstr = text_to_cstring(fc_parameter);

	return pg_parameter_aclcheck(fc_paramstr, fc_roleid, fc_priv) == ACLCHECK_OK;
}

/*
 * has_parameter_privilege_name_name
 *		检查用户在给定名称用户名、文本参数和文本特权名称的参数上的特权。
 */
Datum has_parameter_privilege_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	text	   *fc_parameter = PG_GETARG_TEXT_PP(1);
	AclMode		fc_priv = fc_convert_parameter_priv_string(PG_GETARG_TEXT_PP(2));
	Oid			fc_roleid = get_role_oid_or_public(NameStr(*fc_username));

	PG_RETURN_BOOL(fc_has_param_priv_byname(fc_roleid, fc_parameter, fc_priv));
}

/*
 * has_parameter_privilege_name
 *		检查用户在给定文本参数和文本特权名称的参数上的特权。假定为当前用户
 */
Datum has_parameter_privilege_name(PG_FUNCTION_ARGS)
{
	text	   *fc_parameter = PG_GETARG_TEXT_PP(0);
	AclMode		fc_priv = fc_convert_parameter_priv_string(PG_GETARG_TEXT_PP(1));

	PG_RETURN_BOOL(fc_has_param_priv_byname(GetUserId(), fc_parameter, fc_priv));
}

/*
 * has_parameter_privilege_id_name
 *		检查用户在给定角色 ID、文本参数和文本特权名称的参数上的特权。
 */
Datum has_parameter_privilege_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	text	   *fc_parameter = PG_GETARG_TEXT_PP(1);
	AclMode		fc_priv = fc_convert_parameter_priv_string(PG_GETARG_TEXT_PP(2));

	PG_RETURN_BOOL(fc_has_param_priv_byname(fc_roleid, fc_parameter, fc_priv));
}

/*
 *		has_parameter_privilege 家族的支持例程。
 */

/*
 * convert_parameter_priv_string
 *		将文本字符串转换为 AclMode 值。
 */
static AclMode fc_convert_parameter_priv_string(text *fc_priv_text)
{
	static const priv_map fc_parameter_priv_map[] = {
		{"SET", ACL_SET},
		{"SET WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SET)},
		{"ALTER SYSTEM", ACL_ALTER_SYSTEM},
		{"ALTER SYSTEM WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_ALTER_SYSTEM)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_text, fc_parameter_priv_map);
}

/*
 * pg_has_role 变体
 *		在 SQL 级别，这些都被命名为 "pg_has_role"。
 *		它们接受角色名称、角色 OID、用户名、用户 OID 或隐含用户 = current_user 的各种组合。
 *
 *		结果是一个布尔值：如果用户拥有所指示的特权，则为 true；如果没有，则为 false。
 */

/*
 * pg_has_role_name_name
 *		检查用户在给定名称用户名、名称角色名称和文本特权名称的角色上的特权。
 */
Datum pg_has_role_name_name(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Name		fc_rolename = PG_GETARG_NAME(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	Oid			fc_roleoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid(NameStr(*fc_username), false);
	fc_roleoid = get_role_oid(NameStr(*fc_rolename), false);
	fc_mode = fc_convert_role_priv_string(fc_priv_type_text);

	fc_aclresult = pg_role_aclcheck(fc_roleoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * pg_has_role_name
 *		检查用户在给定名称角色名称和文本特权名称的角色上的特权。
 *		假定为当前用户
 */
Datum pg_has_role_name(PG_FUNCTION_ARGS)
{
	Name		fc_rolename = PG_GETARG_NAME(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	Oid			fc_roleoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_roleoid = get_role_oid(NameStr(*fc_rolename), false);
	fc_mode = fc_convert_role_priv_string(fc_priv_type_text);

	fc_aclresult = pg_role_aclcheck(fc_roleoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * pg_has_role_name_id
 *		检查用户在给定用户名、角色 OID 和文本特权名称的角色上的特权。
 */
Datum pg_has_role_name_id(PG_FUNCTION_ARGS)
{
	Name		fc_username = PG_GETARG_NAME(0);
	Oid			fc_roleoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = get_role_oid(NameStr(*fc_username), false);
	fc_mode = fc_convert_role_priv_string(fc_priv_type_text);

	fc_aclresult = pg_role_aclcheck(fc_roleoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * pg_has_role_id
 *		检查用户在给定角色 OID 和文本特权名称的角色上的特权。
 *		假定为当前用户
 */
Datum pg_has_role_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleoid = PG_GETARG_OID(0);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(1);
	Oid			fc_roleid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleid = GetUserId();
	fc_mode = fc_convert_role_priv_string(fc_priv_type_text);

	fc_aclresult = pg_role_aclcheck(fc_roleoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * pg_has_role_id_name
 *		检查用户在给定角色 ID、名称角色名称和文本特权名称的角色上的特权。
 */
Datum pg_has_role_id_name(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Name		fc_rolename = PG_GETARG_NAME(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	Oid			fc_roleoid;
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_roleoid = get_role_oid(NameStr(*fc_rolename), false);
	fc_mode = fc_convert_role_priv_string(fc_priv_type_text);

	fc_aclresult = pg_role_aclcheck(fc_roleoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 * pg_has_role_id_id
 *		检查用户在给定角色 ID、角色 OID 和文本特权名称的角色上的特权。
 */
Datum pg_has_role_id_id(PG_FUNCTION_ARGS)
{
	Oid			fc_roleid = PG_GETARG_OID(0);
	Oid			fc_roleoid = PG_GETARG_OID(1);
	text	   *fc_priv_type_text = PG_GETARG_TEXT_PP(2);
	AclMode		fc_mode;
	AclResult	fc_aclresult;

	fc_mode = fc_convert_role_priv_string(fc_priv_type_text);

	fc_aclresult = pg_role_aclcheck(fc_roleoid, fc_roleid, fc_mode);

	PG_RETURN_BOOL(fc_aclresult == ACLCHECK_OK);
}

/*
 *		pg_has_role 家族的支持例程。
 */

/*
 * convert_role_priv_string
 *		将文本字符串转换为 AclMode 值。
 *
 * 我们使用 USAGE 来表示角色的特权是否可访问
 * (has_privs)，使用 MEMBER 来表示 is_member，使用 MEMBER WITH GRANT OPTION
 * (或 ADMIN OPTION) 来表示 is_admin。没有与 MEMBER 对应的 ACL 位，
 * 所以我们使用 ACL_CREATE 来欺骗。这种约定仅与 pg_role_aclcheck 共享，如下所示。
 */
static AclMode fc_convert_role_priv_string(text *fc_priv_type_text)
{
	static const priv_map fc_role_priv_map[] = {
		{"USAGE", ACL_USAGE},
		{"MEMBER", ACL_CREATE},
		{"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
		{"USAGE WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
		{"MEMBER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
		{"MEMBER WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)},
		{NULL, 0}
	};

	return fc_convert_any_priv_string(fc_priv_type_text, fc_role_priv_map);
}

/*
 * pg_role_aclcheck
 *		快速简便的 pg_has_role 支持
 */
static AclResult
pg_role_aclcheck(Oid fc_role_oid, Oid fc_roleid, AclMode fc_mode)
{
	if (fc_mode & ACL_GRANT_OPTION_FOR(ACL_CREATE))
	{
		if (is_admin_of_role(fc_roleid, fc_role_oid))
			return ACLCHECK_OK;
	}
	if (fc_mode & ACL_CREATE)
	{
		if (is_member_of_role(fc_roleid, fc_role_oid))
			return ACLCHECK_OK;
	}
	if (fc_mode & ACL_USAGE)
	{
		if (has_privs_of_role(fc_roleid, fc_role_oid))
			return ACLCHECK_OK;
	}
	return ACLCHECK_NO_PRIV;
}


/*
 * 初始化函数（由 InitPostgres 调用）
 */
void initialize_acl(void)
{
	if (!IsBootstrapProcessingMode())
	{
		cached_db_hash =
			GetSysCacheHashValue1(DATABASEOID,
								  ObjectIdGetDatum(MyDatabaseId));

		/*
		 * 在正常模式下，对任何 pg_auth_members（用于 roles_is_member_of()）、pg_authid（用于 
		 * has_rolinherit()）或 pg_database（用于 roles_is_member_of()）行的 syscache 失效设置回调
		 */
		CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
									  fc_RoleMembershipCacheCallback,
									  (Datum) 0);
		CacheRegisterSyscacheCallback(AUTHOID,
									  fc_RoleMembershipCacheCallback,
									  (Datum) 0);
		CacheRegisterSyscacheCallback(DATABASEOID,
									  fc_RoleMembershipCacheCallback,
									  (Datum) 0);
	}
}

/*
 * RoleMembershipCacheCallback
 *		Syscache 失效回调函数
 */
static void fc_RoleMembershipCacheCallback(Datum fc_arg, int fc_cacheid, uint32 fc_hashvalue)
{
	if (fc_cacheid == DATABASEOID &&
		fc_hashvalue != cached_db_hash &&
		fc_hashvalue != 0)
	{
		return;					/* 忽略其他数据库的 pg_database 变更 */
	}

	/* 强制在下次使用时重新计算成员缓存 */
	cached_role[ROLERECURSE_PRIVS] = InvalidOid;
	cached_role[ROLERECURSE_MEMBERS] = InvalidOid;
}


/* 检查指定角色是否设置了 rolinherit */
static bool fc_has_rolinherit(Oid fc_roleid)
{
	bool		fc_result = false;
	HeapTuple	fc_utup;

	fc_utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(fc_roleid));
	if (HeapTupleIsValid(fc_utup))
	{
		fc_result = ((Form_pg_authid) GETSTRUCT(fc_utup))->rolinherit;
		ReleaseSysCache(fc_utup);
	}
	return fc_result;
}


/*
 * 获取指定 roleid 是成员的角色列表
 *
 * 类型 ROLERECURSE_PRIVS 仅递归通过设置了 rolinherit 的角色，
 * 而 ROLERECURSE_MEMBERS 则通过所有角色进行递归。只有当角色
 * "roleid" 在角色 "admin_of" 中具有 ADMIN OPTION 成员资格时，
 * 此设置 *is_admin==true。
 *
 * 由于间接成员资格测试相对昂贵，我们缓存成员列表。
 * 因此，结果仅在下次调用 roles_is_member_of() 之前保证有效！
 *
 * 为了 select_best_grantor 的利益，结果被定义为
 * 广度优先顺序，即，较近的关系优先。
 */
static List * fc_roles_is_member_of(Oid fc_roleid, enum RoleRecurseType fc_type,
				   Oid fc_admin_of, bool *fc_is_admin)
{
	Oid			fc_dba;
	List	   *fc_roles_list;
	ListCell   *fc_l;
	List	   *fc_new_cached_roles;
	MemoryContext fc_oldctx;

	Assert(OidIsValid(fc_admin_of) == PointerIsValid(fc_is_admin));

	/* 如果缓存有效且不寻求 ADMIN OPTION，则直接返回列表 */
	if (cached_role[fc_type] == fc_roleid && !OidIsValid(fc_admin_of) &&
		OidIsValid(cached_role[fc_type]))
		return cached_roles[fc_type];

	/*
	 * 角色扩展发生在非数据库后端，当 guc.c 检查
	 * ROLE_PG_READ_ALL_SETTINGS 时，用于物理 walsender SHOW 命令。
	 * 在这种情况下，角色不会获得 pg_database_owner。
	 */
	if (!OidIsValid(MyDatabaseId))
		fc_dba = InvalidOid;
	else
	{
		HeapTuple	fc_dbtup;

		fc_dbtup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
		if (!HeapTupleIsValid(fc_dbtup))
			elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
		fc_dba = ((Form_pg_database) GETSTRUCT(fc_dbtup))->datdba;
		ReleaseSysCache(fc_dbtup);
	}

	/*
	 * 查找角色 id 是其成员的所有角色，包括多级递归。
	 * 角色本身将始终是结果列表中的第一个元素。
	 *
	 * 列表的每个元素都被扫描以查看其是否添加了任何间接
	 * 成员资格。我们可以使用单个列表作为已找到成员资格的记录
	 * 和待扫描角色的议程。这有点棘手，但可以正常工作，因为
	 * foreach() 宏在循环底部之前不会获取下一个列表元素。
	 */
	fc_roles_list = list_make1_oid(fc_roleid);

	foreach(fc_l, fc_roles_list)
	{
		Oid			fc_memberid = lfirst_oid(fc_l);
		CatCList   *fc_memlist;
		int			fc_i;

		if (fc_type == ROLERECURSE_PRIVS && !fc_has_rolinherit(fc_memberid))
			continue;			/* 忽略不继承的角色 */

		/* 查找 memberid 直接是其成员的角色 */
		fc_memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
									  ObjectIdGetDatum(fc_memberid));
		for (fc_i = 0; fc_i < fc_memlist->n_members; fc_i++)
		{
			HeapTuple	fc_tup = &fc_memlist->members[fc_i]->tuple;
			Oid			fc_otherid = ((Form_pg_auth_members) GETSTRUCT(fc_tup))->roleid;

			/*
			 * 尽管其他 id==InvalidOid 不应该出现在目录中，
			 * OidIsValid() 避免了出现时崩溃。
			 */
			if (fc_otherid == fc_admin_of &&
				((Form_pg_auth_members) GETSTRUCT(fc_tup))->admin_option &&
				OidIsValid(fc_admin_of))
				*fc_is_admin = true;

			/*
			 * 尽管在成员资格图中不应该有任何循环，但我们必须测试
			 * 是否已经看到这个角色。例如，A->B 和 A->C->B 是合法的。
			 */
			fc_roles_list = list_append_unique_oid(fc_roles_list, fc_otherid);
		}
		ReleaseSysCacheList(fc_memlist);

		/* 实现 pg_database_owner 隐式成员资格 */
		if (fc_memberid == fc_dba && OidIsValid(fc_dba))
			fc_roles_list = list_append_unique_oid(fc_roles_list,
												ROLE_PG_DATABASE_OWNER);
	}

	/*
	 * 将完成的列表复制到 TopMemoryContext，以便其将持久化。
	 */
	fc_oldctx = MemoryContextSwitchTo(TopMemoryContext);
	fc_new_cached_roles = list_copy(fc_roles_list);
	MemoryContextSwitchTo(fc_oldctx);
	list_free(fc_roles_list);

	/*
	 * 现在安全地分配给状态变量
	 */
	cached_role[fc_type] = InvalidOid; /* 只是 parano */
	list_free(cached_roles[fc_type]);
	cached_roles[fc_type] = fc_new_cached_roles;
	cached_role[fc_type] = fc_roleid;

	/* 现在我们可以返回答案 */
	return cached_roles[fc_type];
}


/*
 * 成员是否具有角色的权限（直接或间接）？
 *
 * 这被定义为不通过未设置 rolinherit 的角色递归；
 * 对于这样的角色，成员资格意味着能够执行 SET ROLE，
 * 但在执行之前权限不可用。
 */
bool has_privs_of_role(Oid fc_member, Oid fc_role)
{
	/* 简单情况的快速路径 */
	if (fc_member == fc_role)
		return true;

	/* 超级用户拥有所有权限，因此属于每个角色 */
	if (superuser_arg(fc_member))
		return true;

	/*
	 * 查找成员具有权限的所有角色，包括多级递归，
	 * 然后查看目标角色是否是其中之一。
	 */
	return list_member_oid(fc_roles_is_member_of(fc_member, ROLERECURSE_PRIVS,
											  InvalidOid, NULL),
						   fc_role);
}


/*
 * 成员是否是角色的成员（直接或间接）？
 *
 * 这被定义为不管 rolinherit 而递归角色。
 *
 * 不要将此用于权限检查，而是使用 has_privs_of_role()
 */
bool is_member_of_role(Oid fc_member, Oid fc_role)
{
	/* 简单情况的快速路径 */
	if (fc_member == fc_role)
		return true;

	/* 超级用户拥有所有权限，因此属于每个角色 */
	if (superuser_arg(fc_member))
		return true;

	/*
	 * 查找成员是其成员的所有角色，包括多级递归，
	 * 然后查看目标角色是否是其中之一。
	 */
	return list_member_oid(fc_roles_is_member_of(fc_member, ROLERECURSE_MEMBERS,
											  InvalidOid, NULL),
						   fc_role);
}

/*
 * check_is_member_of_role
 *		is_member_of_role，如果没有的话会产生标准权限违反错误
 */
void check_is_member_of_role(Oid fc_member, Oid fc_role)
{
	if (!is_member_of_role(fc_member, fc_role))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be member of role \"%s\"",
						GetUserNameFromId(fc_role, false))));
}

/*
 * 成员是否是角色的成员，不考虑超级用户身份？
 *
 * 这与 is_member_of_role 相同，只是我们忽略超级用户
 * 状态。
 *
 * 不要将此用于权限检查，而是使用 has_privs_of_role()
 */
bool is_member_of_role_nosuper(Oid fc_member, Oid fc_role)
{
	/* 简单情况的快速路径 */
	if (fc_member == fc_role)
		return true;

	/*
	 * 查找成员是其成员的所有角色，包括多级递归，
	 * 然后查看目标角色是否是其中之一。
	 */
	return list_member_oid(fc_roles_is_member_of(fc_member, ROLERECURSE_MEMBERS,
											  InvalidOid, NULL),
						   fc_role);
}


/*
 * 成员是角色的管理员吗？即，成员是否是角色本身（受
 * 以下限制），直接或间接具有 ADMIN OPTION 的成员，
 * 或者是超级用户？
 */
bool is_admin_of_role(Oid fc_member, Oid fc_role)
{
	bool		fc_result = false;

	if (superuser_arg(fc_member))
		return true;

	/* 根据政策，角色不能对自身具有 WITH ADMIN OPTION。 */
	if (fc_member == fc_role)
		return false;

	(void) fc_roles_is_member_of(fc_member, ROLERECURSE_MEMBERS, fc_role, &fc_result);
	return fc_result;
}


/* 如其所示 ... */
static int fc_count_one_bits(AclMode fc_mask)
{
	int			fc_nbits = 0;

	/* 此代码依赖于 AclMode 是无符号类型 */
	while (fc_mask)
	{
		if (fc_mask & 1)
			fc_nbits++;
		fc_mask >>= 1;
	}
	return fc_nbits;
}



/*
 * 选择 GRANT 或 REVOKE 操作的有效授予者 ID。
 *
 * 授予者必须始终是对象所有者或某个已明确授予授予选项的角色。这确保所有授予的特权似乎都来自对象所有者，并且永远不会有多个“原始来源”的特权。因此，如果拟授予者是具有所需授予选项的角色的成员，我们必须以该角色的身份进行授予。
 *
 * 拟授予者可能是多个角色的成员，这些角色具有不同子集的所需授予选项，但是没有一个角色拥有所有选项。在这种情况下，我们选择具有最多所需选项的角色。平局时，优先选择更接近的祖先。
 *
 * roleId：尝试进行 GRANT/REVOKE 的角色
 * privileges：要授予/撤销的特权
 * acl：相关对象的 ACL
 * ownerId：拥有相关对象的角色
 * *grantorId：接收将作为授予者的角色的 OID
 * *grantOptions：接收 grantorId 实际持有的授予选项
 *
 * 如果不存在授予选项，我们将 grantorId 设置为 roleId，将 grantOptions 设置为 0。
 */
void select_best_grantor(Oid fc_roleId, AclMode fc_privileges,
					const Acl *fc_acl, Oid fc_ownerId,
					Oid *fc_grantorId, AclMode *fc_grantOptions)
{
	AclMode		fc_needed_goptions = ACL_GRANT_OPTION_FOR(fc_privileges);
	List	   *fc_roles_list;
	int			fc_nrights;
	ListCell   *fc_l;

	/*
	 * 对象所有者始终被视为拥有所有授予选项，因此如果 roleId 是所有者则很简单。此外，如果 roleId 是超级用户，则也很简单：超级用户是每个角色的隐式成员，因此他们作为对象所有者行事。
	 */
	if (fc_roleId == fc_ownerId || superuser_arg(fc_roleId))
	{
		*fc_grantorId = fc_ownerId;
		*fc_grantOptions = fc_needed_goptions;
		return;
	}

	/*
	 * 否则，我们必须进行仔细搜索，以查看 roleId 是否具有任何合适角色的特权。注意：我们可以在整个循环中保留 roles_is_member_of() 的结果，因为 aclmask_direct() 不会查询任何角色成员资格。
	 */
	fc_roles_list = fc_roles_is_member_of(fc_roleId, ROLERECURSE_PRIVS,
									InvalidOid, NULL);

	/* 将候选结果初始化为默认值 */
	*fc_grantorId = fc_roleId;
	*fc_grantOptions = ACL_NO_RIGHTS;
	fc_nrights = 0;

	foreach(fc_l, fc_roles_list)
	{
		Oid			fc_otherrole = lfirst_oid(fc_l);
		AclMode		fc_otherprivs;

		fc_otherprivs = fc_aclmask_direct(fc_acl, fc_otherrole, fc_ownerId,
									fc_needed_goptions, ACLMASK_ALL);
		if (fc_otherprivs == fc_needed_goptions)
		{
			/* 找到一个合适的授予者 */
			*fc_grantorId = fc_otherrole;
			*fc_grantOptions = fc_otherprivs;
			return;
		}

		/*
		 * 如果它拥有一些必要的权限，记住最佳候选者。
		 */
		if (fc_otherprivs != ACL_NO_RIGHTS)
		{
			int			fc_nnewrights = fc_count_one_bits(fc_otherprivs);

			if (fc_nnewrights > fc_nrights)
			{
				*fc_grantorId = fc_otherrole;
				*fc_grantOptions = fc_otherprivs;
				fc_nrights = fc_nnewrights;
			}
		}
	}
}

/*
 * get_role_oid - 给定角色名称，查找角色的OID。
 *
 * 如果missing_ok为假，当找不到角色名称时抛出错误。如果为真，返回InvalidOid。
 */
Oid get_role_oid(const char *fc_rolname, bool fc_missing_ok)
{
	Oid			fc_oid;

	fc_oid = GetSysCacheOid1(AUTHNAME, Anum_pg_authid_oid,
						  CStringGetDatum(fc_rolname));
	if (!OidIsValid(fc_oid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("role \"%s\" does not exist", fc_rolname)));
	return fc_oid;
}

/*
 * get_role_oid_or_public - 类似于上面，但如果角色名称是"public"则返回ACL_ID_PUBLIC。
 */
Oid get_role_oid_or_public(const char *fc_rolname)
{
	if (strcmp(fc_rolname, "public") == 0)
		return ACL_ID_PUBLIC;

	return get_role_oid(fc_rolname, false);
}

/*
 * 给定一个RoleSpec节点，返回其对应的OID。如果missing_ok为真，角色不存在时返回InvalidOid。
 *
 * 这里总是禁止使用PUBLIC。希望处理PUBLIC情况的例程必须单独检查。
 */
Oid get_rolespec_oid(const RoleSpec *fc_role, bool fc_missing_ok)
{
	Oid			fc_oid;

	switch (fc_role->roletype)
	{
		case ROLESPEC_CSTRING:
			Assert(fc_role->rolename);
			fc_oid = get_role_oid(fc_role->rolename, fc_missing_ok);
			break;

		case ROLESPEC_CURRENT_ROLE:
		case ROLESPEC_CURRENT_USER:
			fc_oid = GetUserId();
			break;

		case ROLESPEC_SESSION_USER:
			fc_oid = GetSessionUserId();
			break;

		case ROLESPEC_PUBLIC:
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("role \"%s\" does not exist", "public")));
			fc_oid = InvalidOid;	/* 使编译器开心 */
			break;

		default:
			elog(ERROR, "unexpected role type %d", fc_role->roletype);
	}

	return fc_oid;
}

/*
 * 给定一个RoleSpec节点，返回其对应的pg_authid HeapTuple。
 * 调用者完成结果元组后必须释放系统缓存。
 */
HeapTuple get_rolespec_tuple(const RoleSpec *fc_role)
{
	HeapTuple	fc_tuple;

	switch (fc_role->roletype)
	{
		case ROLESPEC_CSTRING:
			Assert(fc_role->rolename);
			fc_tuple = SearchSysCache1(AUTHNAME, CStringGetDatum(fc_role->rolename));
			if (!HeapTupleIsValid(fc_tuple))
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_OBJECT),
						 errmsg("role \"%s\" does not exist", fc_role->rolename)));
			break;

		case ROLESPEC_CURRENT_ROLE:
		case ROLESPEC_CURRENT_USER:
			fc_tuple = SearchSysCache1(AUTHOID, GetUserId());
			if (!HeapTupleIsValid(fc_tuple))
				elog(ERROR, "cache lookup failed for role %u", GetUserId());
			break;

		case ROLESPEC_SESSION_USER:
			fc_tuple = SearchSysCache1(AUTHOID, GetSessionUserId());
			if (!HeapTupleIsValid(fc_tuple))
				elog(ERROR, "cache lookup failed for role %u", GetSessionUserId());
			break;

		case ROLESPEC_PUBLIC:
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("role \"%s\" does not exist", "public")));
			fc_tuple = NULL;		/* 使编译器开心 */
			break;

		default:
			elog(ERROR, "unexpected role type %d", fc_role->roletype);
	}

	return fc_tuple;
}

/*
 * 给定一个RoleSpec，返回对应角色名称的palloc副本。
 */
char * get_rolespec_name(const RoleSpec *fc_role)
{
	HeapTuple	fc_tp;
	Form_pg_authid fc_authForm;
	char	   *fc_rolename;

	fc_tp = get_rolespec_tuple(fc_role);
	fc_authForm = (Form_pg_authid) GETSTRUCT(fc_tp);
	fc_rolename = pstrdup(NameStr(fc_authForm->rolname));
	ReleaseSysCache(fc_tp);

	return fc_rolename;
}

/*
 * 给定一个RoleSpec，如果名称被保留则抛出错误，使用detail_msg（如果提供）。
 *
 * 如果节点为NULL，则不抛出错误。如果detail_msg为NULL，则不提供详细消息。
 */
void check_rolespec_name(const RoleSpec *fc_role, const char *fc_detail_msg)
{
	if (!fc_role)
		return;

	if (fc_role->roletype != ROLESPEC_CSTRING)
		return;

	if (IsReservedName(fc_role->rolename))
	{
		if (fc_detail_msg)
			ereport(ERROR,
					(errcode(ERRCODE_RESERVED_NAME),
					 errmsg("role name \"%s\" is reserved",
							fc_role->rolename),
					 errdetail_internal("%s", fc_detail_msg)));
		else
			ereport(ERROR,
					(errcode(ERRCODE_RESERVED_NAME),
					 errmsg("role name \"%s\" is reserved",
							fc_role->rolename)));
	}
}
